Compare commits

..

461 Commits

Author SHA1 Message Date
c30ae9594c release 2014.11.02.1 2014-11-02 10:28:21 +01:00
ffae28ae18 release 2014.11.02 2014-11-02 09:45:51 +01:00
d9116714f2 [cinemassacre] Fix extraction (Closes #4083) 2014-11-02 08:01:14 +07:00
08965906a8 [README] Update FAQ on Ubuntu (#4078) 2014-11-01 19:24:56 +01:00
5263cdfcf9 [generic] Improve MLB iframe regex 2014-11-01 04:01:58 +07:00
b2a68d14cf [mlb] Improve _VALID_URL (Closes #4063) 2014-11-01 04:01:18 +07:00
6e1cff9c33 [canalplus] Improve and merge with d8 extractor 2014-10-31 21:54:30 +07:00
72975729c8 [canalplus] Tweak extractor to support piwiplus (Closes #4046) 2014-10-31 20:19:30 +07:00
d319948b6a [funnyordie] Add articles URL test 2014-10-31 19:26:56 +07:00
9a4bf889f9 Merge pull request #4069 from anovicecodemonkey/support_funnyordie_articles_urls
[FunnyOrDie] Add support for "/articles/" URLs
2014-10-31 18:25:22 +05:00
2a834bdb21 [FunnyOrDie] Add support for "/articles/" URLs 2014-10-31 21:20:37 +10:30
0d2c141865 [youtube] Detect formats 298 et al as mp4 (Fixes #4066) 2014-10-31 11:13:02 +01:00
5ec39d8b96 release 2014.10.30 2014-10-30 09:53:48 +01:00
7b6de3728a [youtube] Add format 266 (Fixes #4055) 2014-10-30 09:53:43 +01:00
a51d3aa001 [youtube] Add support for formats 302 and 303 (Fixes #4060) 2014-10-30 09:43:11 +01:00
2c8e03d937 Sort formats by fps as well 2014-10-30 09:40:52 +01:00
fbb21cf528 [youtube] Add formats 298, 299 (Fixes #4056) 2014-10-30 09:34:13 +01:00
b8a618f898 [ro220] Fix broken extractor and modernize (#4054) 2014-10-30 01:42:52 +02:00
feb74960eb release 2014.10.29 2014-10-29 23:29:42 +01:00
d65d628613 [crunchycroll] Fix building of ass subtitles (reported in #4019)
Parse the xml document instead of using regexes, otherwise unicode characters are left unescaped.
2014-10-29 21:19:20 +01:00
ac645ac7d0 [generic] Allow soundcloud embeds with additional attributes 2014-10-29 20:27:58 +01:00
7d11297f3f Merge branch 'master' of github.com:rg3/youtube-dl 2014-10-29 20:10:07 +01:00
6ad4013d40 [drtv] Allow fractional timestamps (Fixes #4059) 2014-10-29 20:10:00 +01:00
dbd1283d31 [naver] Capture and output error message (#4057) 2014-10-29 21:50:37 +07:00
c451d4f553 [trutube] Fix extraction 2014-10-29 21:16:10 +07:00
8abec2c8bb [test_utils] Fix compat_getenv and compat_expanduser tests on python 3.x 2014-10-29 11:13:34 +01:00
a9bad429b3 [niconico] Add extractor for playlists (closes #4043) 2014-10-29 11:04:48 +01:00
50c8266ef0 Merge branch 'master' of github.com:rg3/youtube-dl 2014-10-28 23:40:44 +01:00
00edd4f9be [laola1tv] Mark as broken
When the f4m downloader gets live stream support, I expect this to work magically or with very minor changes.
2014-10-28 17:29:27 +01:00
ee966928af [f4m] Support bootstrap URLs 2014-10-28 17:27:41 +01:00
e5193599ec [laola1tv] Add new extractor
The extractor works fine, but the f4m downloader cannot handle the resulting bootstrap information.
2014-10-28 16:51:34 +01:00
01d663bca3 [auengine] Simplify 2014-10-28 15:51:15 +01:00
e0c51cdadc [vk] Generalize errors 2014-10-28 21:35:25 +07:00
9334f8f17a [vk] Handle deleted videos 2014-10-28 21:06:07 +07:00
632256d9ec [wimp] Update video URL regex 2014-10-28 20:35:02 +07:00
03df7baa6a Start documentation on how to embed youtube-dl 2014-10-28 12:54:39 +01:00
3511266bc3 [YoutubeDL] Simplify API of YoutubeDL
Calling add_default_extractors twice should be harmless since the first set of extractors will match.
2014-10-28 12:54:29 +01:00
9fdece5d34 [srmediathek] Choose variable name more wisely 2014-10-28 10:44:47 +01:00
bbf1092ad0 [fktv] Remove unused import 2014-10-28 10:44:17 +01:00
9ef55c5bbc [quickvid] Add new extractor 2014-10-28 10:41:37 +01:00
48a24ab746 [generic] Fix HTML5 video regexp 2014-10-28 10:41:24 +01:00
68acdbda9d Merge remote-tracking branch 'origin/master' 2014-10-28 09:13:23 +01:00
27c542c06f [iconosquare] Simplify 2014-10-28 09:12:28 +01:00
aaa399d2f6 [sphinx] Fix version import 2014-10-27 18:49:48 +02:00
b2e6a1c14c release 2014.10.27 2014-10-27 02:44:07 +01:00
8cc3eba79a [phoenix] Add new extractor (Fixes #4036) 2014-10-27 02:43:59 +01:00
b0fb6d4db1 [ku6] Modernize 2014-10-27 02:32:44 +01:00
81515ad9f6 [extractor/common] Improve m3u8 output 2014-10-27 02:28:37 +01:00
8112d4b284 [lrt] Modernize 2014-10-27 02:27:49 +01:00
bf7aa6301b [fktv] Modernize 2014-10-27 02:26:05 +01:00
aea856621f [zdf] Simplify 2014-10-27 02:14:07 +01:00
f24a5a2faa Merge remote-tracking branch 'olebowle/ard' 2014-10-27 01:36:50 +01:00
ecfe623422 [heise] Fix extraction
Now they use an XML format instead of JSON.
2014-10-27 01:33:51 +01:00
4a6c94288a [kickstarter] Simplify and fix test case 2014-10-27 01:16:18 +01:00
10e3d73472 [nbc] Fix ThePlatform embedded videos 2014-10-27 01:14:17 +01:00
15956b5aa1 [promptfile] Fix check for deleted videos 2014-10-27 00:50:22 +01:00
586f7082ef [francetv] Remove changing md5sum 2014-10-27 00:46:34 +01:00
d6d9186f0d [generic] Fix test title 2014-10-27 00:45:15 +01:00
2e9ff8f362 [gorillavid] Fix test title 2014-10-27 00:44:27 +01:00
6407432333 [Makefile] remove temporary files in clean target 2014-10-27 00:40:07 +01:00
f744c0f398 [test_download] Improve error message 2014-10-27 00:39:39 +01:00
249efaf44b [pornhub] Modernize and fix test definition 2014-10-27 00:33:35 +01:00
8d32abff9e [ruhd] Simplify 2014-10-27 00:20:54 +01:00
94f052cbf4 [syfy] Remove test checksum
We have the minsize test now.
2014-10-27 00:19:15 +01:00
446a03bd96 [ustream:channel] Change test playlist size (Seems to have been limited that way on the website as well) 2014-10-27 00:18:10 +01:00
6009b69f81 [vgtv] Fix test title 2014-10-27 00:16:01 +01:00
3d6047113c [vgtv] Simplify 2014-10-27 00:14:52 +01:00
9dec99303d [vimeo:review] Fix test title 2014-10-27 00:13:40 +01:00
7706927370 [vine:user] Adapt test to changed list size 2014-10-27 00:11:34 +01:00
3adba6fa2a [xtube] Fix test description 2014-10-27 00:08:37 +01:00
f46a8702cc [youtube:playlist] Fix test title 2014-10-27 00:06:47 +01:00
8d11b59bbb [ynet] Remove test md5sums
These fluctuate regularly.
2014-10-27 00:06:00 +01:00
cf501a23d2 [srmediathek] Correct IE_NAME/IE_DESC 2014-10-26 23:23:53 +01:00
2bcae58d46 [srmediathek] New extractor 2014-10-26 23:23:10 +01:00
c9f08154a3 Remove unused imports 2014-10-26 23:13:42 +01:00
526b276fd7 [faz] Modernize 2014-10-26 23:11:15 +01:00
77ec444d9a release 2014.10.26.2 2014-10-26 21:49:52 +01:00
bfc2bedcfc [youtube] Make confirm_age non-fatal (#4042) 2014-10-26 21:49:29 +01:00
83855f3a1f [livestream:original] Fix RTMP parameters (Fixes #4040) 2014-10-26 21:44:29 +01:00
50b51830fb [ffmpeg] Fix typo 2014-10-26 21:31:51 +01:00
3d6eed9b52 release 2014.10.26.1 2014-10-26 21:03:38 +01:00
1a253e134c [ffmpeg] Fix call to ffprobe (Fixes #4041) 2014-10-26 21:03:16 +01:00
6194bb1419 [ffmpeg] Make downloader optional (Fixes #4039) 2014-10-26 21:00:42 +01:00
37d66e7f1e [generic] Correct call to _webpage_read_full_content 2014-10-26 20:58:09 +01:00
70b7e3fbb6 [generic] Add a test case for direct links with broken HEAD (#4032) 2014-10-26 20:49:51 +01:00
579657ad87 [soundcloud] Set the 'webpage_url' field for each track
For playlists, YoutubeDL would set it to the playlist url.
2014-10-26 19:08:36 +01:00
5f82b129e0 [ffmpeg] Also look into stderr for extracting the version
At least with avconv 11, it will print 'avconv version 11, ..' to stderr, not stdout.
2014-10-26 18:11:31 +01:00
64269e4d01 Move AUTHORS to root (closes #2985) 2014-10-26 18:01:00 +01:00
d481699a7a release 2014.10.26 2014-10-26 17:29:27 +01:00
5894a4f4ee Credit @gabeos for crunchyroll:playlist (#3988) 2014-10-26 17:29:02 +01:00
09e5d6a6e5 [crunchyroll:playlist] Simplify (#3988) 2014-10-26 17:28:09 +01:00
274b12b5a8 Merge remote-tracking branch 'gabeos/crunchyroll-show-playlist' 2014-10-26 17:06:35 +01:00
23be51d8ce [generic] Handle audio streams that do not implement HEAD (Fixes #4032) 2014-10-26 17:05:44 +01:00
488447455d [ffmpeg] Warn if ffmpeg/avconv version is too old (Fixes #4026) 2014-10-26 16:46:34 +01:00
d28b517154 [YoutubeDL] Output avconv/ffmpeg versions if -v is given 2014-10-26 16:31:52 +01:00
a7e97f6db1 [generic] Allow new SWFObject()-style imports
This embed style is used on http://www.bitburger-open.de/ , but that is not included as a test case since the format is likely to be temporary.
2014-10-26 14:15:49 +01:00
639a422d21 Merge branch 'dstftw-compat-getenv-and-expanduser' 2014-10-26 19:58:39 +07:00
f889cea109 Merge branch 'compat-getenv-and-expanduser' of https://github.com/dstftw/youtube-dl into dstftw-compat-getenv-and-expanduser
Conflicts:
	test/test_utils.py
	youtube_dl/__init__.py
2014-10-26 19:56:52 +07:00
1bdeb7be2e Set '--simulate' if any of the printing options is given (fixes #3036)
That's what the help messages say.
Previously it would only set '--skip-download', which would write thumbnail,
descriptions or subtitles if they were requested (for example you may have set
'--write-thumbnail' in the config file).
2014-10-26 13:01:57 +01:00
699151bcb1 Merge branch 'Dineshs91-belgiannational-ie' 2014-10-26 16:48:55 +07:00
911344e5ac [vrt] Improve extractor 2014-10-26 16:48:11 +07:00
03936f6e6d [BelgianNational] corrected indentation 2014-10-26 13:47:41 +05:30
b13ccb1b87 [BelgianNational] New extractor added 2014-10-26 13:35:00 +05:30
f64f8a4662 [sportbox] Remove view count 2014-10-26 08:00:01 +07:00
681b9caa9c [tumblr] Fix extraction (fixes #4029) 2014-10-25 22:42:56 +02:00
0eb9fb9f24 [soundcloud] Modernize and fix tests 2014-10-25 22:32:01 +02:00
9a76f416ce [lrt] Updated test 2014-10-25 13:24:46 +03:00
603821161f Merge branch 'master' of github.com:rg3/youtube-dl 2014-10-25 09:55:05 +02:00
d3c72db894 [audiomack] Simplify 2014-10-25 08:58:03 +02:00
43d9718fb9 [nhl] Improve video URL extraction (Closes #4013) 2014-10-25 13:56:21 +07:00
7fc54e5262 Merge remote-tracking branch 'xavierbeynon/audiomack' 2014-10-25 08:55:12 +02:00
ec9c978481 Credit @winwon for vidzi (#3989) 2014-10-25 08:42:53 +02:00
d36cae46d8 Not directly calling soundcloud extractor anymore 2014-10-24 21:11:46 -05:00
fdfefa1b9c Made changes per phihag 2014-10-24 21:07:01 -05:00
724d031893 release 2014.10.25 2014-10-25 00:41:06 +02:00
63e0be3415 New option --dump-single-json (#4003) 2014-10-25 00:30:57 +02:00
c64ed2a310 [viddler] Use API 2014-10-25 00:11:12 +02:00
cdc5cb7c2b [hark] Modernize 2014-10-24 22:31:55 +02:00
8efd06aa42 [motherless] Simplify 2014-10-24 19:53:48 +02:00
7f9ced64cb Add group videos support for motherless
Modified motherless regular expression.
Previously it matched only URLs like this:
motherless.com/ID
Now it also matches this:
motherless.com/g/group_name/ID
All tests including the newly added one passed.
2014-10-24 21:44:21 +04:00
7608815cc2 Add another motherless test
This test is for videos posted in groups.
URL looks like this:
motherless.com/g/group_name/ID
which is basically the same as
motherless.com/ID
So far this test fails because I haven't improved the extractor yet.
2014-10-24 21:43:27 +04:00
5823eda139 Fix motherless test 0
Apparently motherless no longer serves flv for that video (or maybe even
other videos). So I changed expected extension from flv to mp4 and
changed expected md5 accordingly.
2014-10-24 21:26:32 +04:00
e82c1e9a6e [YoutubeDL] Do not apply playlist info to videos when extract_flat is set (#4003) 2014-10-24 16:13:45 +02:00
1ede5b2481 [glide] Simplify 2014-10-24 15:34:19 +02:00
964ae0a122 Credit @thornomad for glide (#3944) 2014-10-24 15:29:44 +02:00
98e1d28982 Merge remote-tracking branch 'thornomad/glide' 2014-10-24 15:29:03 +02:00
2c26df763c [vidzi] Use proper test case and simplify (#3989) 2014-10-24 15:27:02 +02:00
018e835594 [vidzi] Simplify 2014-10-24 15:17:17 +02:00
e65e06fbe2 [vidzi] Correct order in imports (#3989) 2014-10-24 15:15:54 +02:00
95ee84421e vidzi Add new extractor 2014-10-24 15:15:13 +02:00
2acfe95f58 Credit @capital-G for bild.de (#3983) 2014-10-24 15:12:29 +02:00
b5a14350b9 [bild] Simplify (#3983) 2014-10-24 15:10:32 +02:00
8d81f872fb Merge remote-tracking branch 'capital-G/master' 2014-10-24 15:02:50 +02:00
36f1c90497 release 2014.10.24 2014-10-24 14:48:19 +02:00
057a5206cc Add --flat-playlist option (Closes #4003) 2014-10-24 14:48:12 +02:00
9e9bc793f3 Finished audiomack extractor 2014-10-23 23:54:59 -05:00
5c565ac9e7 Added init.py initializer 2014-10-23 16:58:11 -05:00
67500bf939 Initial version of audiomack.py 2014-10-23 16:55:39 -05:00
b1edd7a48a [crunchyroll] Correct parsing (Fixes #4014) 2014-10-23 23:25:02 +02:00
2c63ccec78 [mitele] Fix on python 2.x 2014-10-23 21:26:48 +02:00
f2f2c0c2c6 [generic] Allow --default-search without colon 2014-10-23 21:13:45 +02:00
4661e243f8 release 2014.10.23 2014-10-23 20:21:38 +02:00
f3cd403c2b [telecino] Add coding declaration 2014-10-23 20:21:27 +02:00
ad5f53ac72 [telecinco] Add extractor (closes #4005)
It uses the same extraction process as mitele.es, but with a few small differences.
2014-10-23 20:08:55 +02:00
75da98e9e1 [funnyordie] Fix extraction (Closes #4011) 2014-10-23 23:07:58 +07:00
281d3f1d68 [generic/wistia] Improve regex 2014-10-23 23:03:07 +07:00
6283c10b1c Merge pull request #4009 from Dineshs91/thoughtworks
[thoughtworks] wistia support added
2014-10-23 22:59:53 +07:00
85d7b76586 [thoughtworks] wistia regex modified 2014-10-23 21:23:56 +05:30
2399535fd1 [francetv] Lower preference of direct links (Closes #4010, closes #3947)
Direct links fail with 403, fallback on f4m for now until further investigations.
2014-10-23 22:30:34 +07:00
52cffcb186 [thoughtworks] wistia support added 2014-10-23 20:28:39 +05:30
8f3b5397a7 [cinemassacre] Fix extraction (Closes #4008) 2014-10-23 21:35:51 +07:00
9bbec55255 [pbs] Add support for tabbed frontline videos (Closes #4006) 2014-10-23 20:41:45 +07:00
6b445558ff [youtube] Only set language when necessary
We only need to set the language when we are using a login; otherwise it will always be English.
2014-10-23 00:44:22 +02:00
bfd91588f3 [ard] make rss match more universal 2014-10-22 14:24:53 +02:00
6bf6962062 [francetv] Force m3u8 formats extension to mp4 (Closes #3997) 2014-10-22 01:24:04 +07:00
40bca5f927 [arte.tv] Remove unused import 2014-10-21 21:06:24 +07:00
74214d35c5 [arte.tv:+7] Improve title extraction (Closes #3995) 2014-10-21 20:08:20 +07:00
1b10a011ec Forgot to reverse extracted video urls so they are in correct order for video selection args 2014-10-20 18:38:42 -07:00
d24a2b20b4 [arte.tv:+7] Use original format ids to avoid duplicates 2014-10-20 20:27:59 +07:00
8230018c20 Added extractor for crunchyroll 'playlists' i.e. series. so that one can, e.g. download all episodes of a series 2014-10-19 22:47:05 -07:00
cc98a3f096 [cnn] Fix for urls ending in '.cnn-ap' (fixes #3985) 2014-10-19 15:14:37 +02:00
ce519b747e added "bild.de" as extractor 2014-10-18 22:15:47 +02:00
16efb3695f [sexykarma] Add support for watchindianporn 2014-10-19 01:53:15 +07:00
4510d14f0a [twitch] Update tests 2014-10-19 01:35:53 +07:00
0f175a932f release 2014.10.18 2014-10-18 20:22:23 +02:00
849b269273 Merge branch 'CkuT-sexykarma' 2014-10-19 00:48:30 +07:00
95fa5fb569 [sexykarma] Improve and simplify 2014-10-19 00:48:05 +07:00
77c3c5c5ed Merge branch 'sexykarma' of https://github.com/CkuT/youtube-dl into CkuT-sexykarma 2014-10-19 00:06:53 +07:00
159444a668 [twitch] Remove superfluous comma 2014-10-18 21:57:24 +07:00
f9befee1f5 [arte.tv:+7] Append media type to format_id (Closes #3967) 2014-10-18 18:14:49 +07:00
9471c44405 [generic] Make sure Wistia embed URLs contain the protocol (Closes #3977)
Also, improve detection (Addresses #3662)
2014-10-18 01:55:21 +03:00
013bfdd84c [twitch] Update tests and minor improvements 2014-10-17 23:46:53 +07:00
46fd0dd5a5 [twitch] Rename extractor and support channel videos 2014-10-17 22:58:18 +07:00
4698f0d858 [vimeo] Improve regex for the config js dict (fixes #3955, fixes #3974)
The javascript code contains assignments of empty dicts with the same variable name
2014-10-17 15:55:58 +02:00
355d074ff9 [twitch] Adapt to new API (Fixes #3946, Fixes #3949, Fixes #3965)
Work in progress
2014-10-16 22:23:35 +07:00
7da224c907 Add categories 2014-10-15 22:34:35 +02:00
1723edb1a5 Few improvements 2014-10-15 20:17:07 +02:00
4740864508 [SexyKarma] Add new extractor 2014-10-15 18:24:32 +02:00
09a42738fc [generic] Correct handling of embedded vimeo players (#3955) 2014-10-15 13:50:53 +02:00
df928d500f release 2014.10.15 2014-10-15 12:39:30 +02:00
a72cbfacf0 [ted] Add support for external videos (fixes #3948) 2014-10-15 12:24:11 +02:00
62a164e713 [mixcloud] Output downloading progress 2014-10-15 00:53:54 +02:00
5f58165def [extractor/common] Fix dumping requests with long file abspath on Windows 2014-10-14 21:43:48 +07:00
a86c73cf80 [glide] Add new extractor
Added an extractor for glide.me shared messages.  Glide is a movile video messaging services.  You can share the link to the messages easily enough and this would allow you to download and save the actual video.
2014-10-13 14:08:29 -07:00
bd4e40df1a [brightcove] Add a test for playlists 2014-10-13 16:26:53 +02:00
1419fafd36 [condenast] Add support for embedded videos (Closes #3929) 2014-10-13 19:59:35 +07:00
9b36dcbd65 release 2014.10.13 2014-10-13 10:12:51 +02:00
2aefb886fa [ffmpeg] Improve format merging (Closes #3935) 2014-10-13 10:12:43 +02:00
72961c2a8a Merge remote-tracking branch 'Dineshs91/KontrTube-fix' 2014-10-13 10:09:57 +02:00
4c1ce987b0 [huffpost] Modernize 2014-10-13 10:08:59 +02:00
8a2300a597 [kontrtube] Fix video title extraction 2014-10-13 10:03:55 +05:30
1cc887cbf0 [youtube] Add support for format 278 2014-10-13 00:09:19 +02:00
203fb43f36 [youtube] Download DASH manifest by default (Closes #3887) 2014-10-13 00:03:08 +02:00
4d7b03f1f2 [zsh-completion] Ignore generated file (#3890) 2014-10-12 23:31:15 +02:00
72ebb5e4b4 Merge remote-tracking branch 'xu-cheng/zsh-completion' 2014-10-12 23:30:17 +02:00
8450c15c25 [options] Consistent formatting and general niceness 2014-10-12 23:10:11 +02:00
b88b45e46c [options] Mention login in --username documentation (#3753) 2014-10-12 22:45:36 +02:00
2417dc1715 release 2014.10.12 2014-10-12 22:25:18 +02:00
23d83ad4d5 [niconico] Fix ignored --netrc flag
See issue #3753
2014-10-12 23:18:42 +03:00
772ece3571 Merge pull request #3932 from Dineshs91/ndr-fix
Description changed
2014-10-12 17:54:51 +02:00
2c9f31188b Description changed 2014-10-12 20:09:12 +05:30
d18be55533 [theonion] Fix a small mistake in string formatting 2014-10-12 15:47:31 +03:00
ac20fc047a [theonion] Add new extractor (closes #3928) 2014-10-12 15:42:35 +03:00
b4c3c8c172 [mixcloud] Fix metadata extraction (fixes #3930) 2014-10-12 13:06:31 +02:00
3357110a4c [vimeo] Make the protocol mandatory in the url (fixes #3926)
If it's missing, it will be correctly handled by the generic IE.
2014-10-11 22:26:26 +02:00
e29fdedb45 Merge pull request #3923 from Dineshs91/howstuffworks-fix
Replace 404 url
2014-10-12 01:48:11 +07:00
4828703f14 [googleplus] Modernize and extract all formats 2014-10-12 01:44:13 +07:00
afe08e0d4a Merge pull request #3924 from Dineshs91/googleplus-fix
Fix download error in GooglePlus
2014-10-12 00:48:58 +07:00
071420e136 Fix download error in GooglePlus 2014-10-11 21:10:53 +05:30
f4cf848d1d Replace 404 url 2014-10-11 15:59:42 +05:30
b7b2ca6e2b Merge pull request #3921 from Dineshs91/ndr-fix
Fix ndr.de outdated test url
2014-10-11 16:07:39 +07:00
1409704afa Fix ndr.de outdated test url 2014-10-11 12:20:13 +05:30
3741302a10 [ard] Add rss support 2014-10-10 20:35:34 +02:00
c8e390c2b0 Merge pull request #3911 from Dockheas23/master
KeyError on initialising YoutubeDL in python3 #3910
2014-10-10 22:28:18 +07:00
823f1e015a [yahoo] Wipe out yahoo news extractor 2014-10-10 22:18:37 +07:00
3c06d3715e [yahoo] Generalize, support arbitrary subdomains, support iframe videos, capture error message (Closes #2470) 2014-10-10 22:11:30 +07:00
762958d5af [yahoo] Add support for regional subdomains and extract duration (Closes #3915) 2014-10-10 19:50:29 +07:00
53d9009bdb KeyError on initialising YoutubeDL in python3 #3910 2014-10-10 10:03:24 +01:00
1b725173a5 Fixed typo 2014-10-10 09:35:41 +01:00
0ca41c3d9c [walla] Fix typo 2014-10-09 21:10:07 +07:00
fc6861b175 [sportbox] Add extractor (Closes #3906) 2014-10-09 21:05:39 +07:00
b097b5f246 [mlb] Remove unused import 2014-10-09 20:07:34 +07:00
385009fc44 [mlb] Fix thumbnails extraction (Closes #3905) 2014-10-09 19:56:55 +07:00
ced659bb4d [generic] Ignore some non-video file extensions during generic extraction (Closes #3900) 2014-10-09 19:26:23 +07:00
842cca7d56 [pornhd] Fix formats extraction (Closes #3898) 2014-10-08 20:08:29 +07:00
b3826f6c8d Merge branch 'lenaten-walla' 2014-10-07 22:23:22 +07:00
7bc8780c57 [walla] Fix extractor and add subtitle tests 2014-10-07 22:23:05 +07:00
c59c3c84ed Merge branch 'walla' of https://github.com/lenaten/youtube-dl into lenaten-walla 2014-10-07 20:24:52 +07:00
24f7fb5e1e add zsh completion support into Makefile 2014-10-07 13:19:59 +08:00
3b700f8d43 support zsh completion 2014-10-07 13:19:59 +08:00
net
31d06400ec add missed init file 2014-10-06 03:03:05 +03:00
642b76ac15 release 2014.10.05.2 2014-10-05 22:04:02 +02:00
4c4de296d4 release 2014.10.05.1 2014-10-05 22:00:16 +02:00
b10609d98c [dailymotion] Alternative title search (Fixes #3882) 2014-10-05 21:59:53 +02:00
3ae165aa10 [gorillavid] Add check for non existing videos 2014-10-06 01:48:01 +07:00
e4b85e35d0 [gorillavid] Fix title extraction and make thumbnail optional (Closes #3884) 2014-10-06 01:47:22 +07:00
bb0c206f59 release 2014.10.05 2014-10-05 07:53:25 +02:00
b81f484b60 [gorillavid] Add support for movpod.in (Fixes #3881) 2014-10-05 07:53:02 +02:00
5e69192ef7 [thesixtyone] Add new extractor (closes #3781) 2014-10-04 22:40:36 +03:00
e9be9a6acd [utils] Add additional format to unified_strdate 2014-10-04 22:38:23 +03:00
f47754f061 [globo] Initial extractor implementation (Closes #3823) 2014-10-04 18:56:36 +07:00
d838b1bd4a [utils] Default age_limit to None
If we can't parse it, it means we don't have any information, not that the content is unrestricted.
2014-10-03 20:17:12 +02:00
fe506288bd [planetaplay] Add new extractor (closes #3839) 2014-10-03 19:43:36 +03:00
d397c0b3dd [breakcom] Extract all formats 2014-10-03 19:37:47 +07:00
146c80e256 [utils] Add parse_age_limit 2014-10-03 19:37:25 +07:00
f78c01f68b [breakcom] Cover more URLs with _VALID_URL (Closes #3876) 2014-10-03 18:57:18 +07:00
8489578df4 [generic] Support embedded Dailymotion playlists (fixes #3822) 2014-10-02 21:42:45 +03:00
10606050bc release 2014.10.02 2014-10-02 15:30:19 +02:00
d9bf465200 [bandcamp] Fix id extraction and modernize (Closes #3866) 2014-10-02 20:22:46 +07:00
01384d6e4b [jpopsuki] More modernize 2014-10-02 19:58:28 +07:00
08d5230945 [jpopsukitv] Improve _VALID_URL and modernize 2014-10-02 19:43:25 +07:00
852f8641e8 Merge pull request #3865 from diffycat/jpopsuki
[jpopsuki] Support category links
2014-10-02 19:38:29 +07:00
18937a50a4 [dropbox] Fix the video url query (fixes #3868)
Previously it would convert urls without a query into 'something.mp4&dl=1'
2014-10-01 23:19:56 +02:00
net
e4d6cca0c1 [walla] Add new extractor 2014-10-01 23:45:35 +03:00
d5feab9aaa [jpopsuki] Support category links 2014-10-01 23:24:23 +04:00
9e77c60c9a [tapely] Catch SoundCloud tracks 2014-10-01 21:53:45 +03:00
1414df5ce2 [izlesene] Prepend scheme to thumbnails 2014-10-01 21:11:38 +03:00
e80f40e5ca [tapely] Add new extractor (closes #3861) 2014-10-01 17:26:09 +03:00
d3c9af84fc [spankwire] Fix extraction 2014-10-01 20:53:58 +07:00
59d206ca2d [sunporno] Fix duration extraction and make more robust 2014-10-01 20:44:43 +07:00
fc66e4a0d5 [utils] Add posix expanduser implementation and clarify the original source 2014-10-01 19:48:55 +07:00
e7b6d12254 [utils] Improve and test js_to_json 2014-10-01 00:08:34 +02:00
410f3e73ab [utils] Fix js_to_json 2014-10-01 00:08:28 +02:00
07e764439a [generic] Delete test case
The page is not available any more.
2014-10-01 00:08:19 +02:00
f8fb4a7ca8 [nfl] Use compatible urlparse 2014-09-30 20:01:37 +03:00
4644ac5527 [core] Decode environment variables with filesystem encoding (Fixes #3854, Fixes #3217, Fixes #2918)
Introduces compat versions of os.getenv and os.path.expanduser
2014-09-30 22:27:53 +07:00
e497a7f2ca [tvigle] Extract format file sizes 2014-09-30 20:00:21 +07:00
a3b6be104d [tvigle] Replace 404 test 2014-09-30 19:55:30 +07:00
b7bb0df21e [vgtv] Fix tests' exts 2014-09-30 19:50:14 +07:00
4dc19c0982 [lrt] Add new extractor 2014-09-30 02:26:16 +03:00
58ea7ec81e [vimeo] Fix description extraction 2014-09-29 22:23:21 +02:00
c0f64ac689 [test/helper] Improve output of missing test definition dictionaries 2014-09-29 22:19:11 +02:00
7a08ad7d59 [test/helper] Modernize 2014-09-29 22:11:24 +02:00
2d29ac4f23 [vuclip] Fix regexp 2014-09-29 21:48:44 +02:00
a7a747d687 [vuclip] Remove test code 2014-09-29 21:47:57 +02:00
fdb4d278bf [spankwire] Fix extraction and modernize 2014-09-29 20:11:51 +07:00
59c03a9bfb [vuclip] Fix extraction 2014-09-29 13:07:58 +02:00
e7db973328 [yahoo] Remove test case
This video seems to have been removed entirely
2014-09-29 12:45:57 +02:00
99b67fecc5 [arte] Fix upload date extraction 2014-09-29 12:45:18 +02:00
89294b5f50 [README] Explain updating in detail (Fixes #3850) 2014-09-29 12:33:29 +02:00
72d53356f6 [internetvideoarchive] Fix test case 2014-09-29 12:24:48 +02:00
9e1e67fc15 [internetvideoarchive] Modernize 2014-09-29 12:23:52 +02:00
1e60e5546e [funnyordie] Fix test case md5 2014-09-29 12:20:25 +02:00
457749a703 [prosiebensat1] Fix test case 2014-09-29 12:18:49 +02:00
937f935db0 [jukebox] Remove md5 sum, it fluctuates 2014-09-29 12:15:49 +02:00
80bcefcd77 [cliphunter] Remove duration 2014-09-29 06:22:54 +02:00
8c23945c72 [eporner] Adapt to changed default format 2014-09-29 06:19:18 +02:00
989b4b2b86 [utils:YoutubeDLHandler] Work around brain-dead Python 2.6 httplib
In 2.6, the httplib sends fragments! Remove those (fixes generic_26 on 2.6).
2014-09-29 06:15:46 +02:00
2a7b4681c6 [godtube] Fix on Python 2.6 2014-09-29 05:51:41 +02:00
8157ae3904 [golem] Fix under 2.6
It's a sad story; 2.6 does not support any non-trivial xpaths.
2014-09-29 05:48:56 +02:00
e50e2fcd4d [br] fix test case 2014-09-29 05:40:20 +02:00
6be451f422 [youtube] Remove swf signature test cases
These files are now 0 Bytes
2014-09-29 05:24:00 +02:00
5e4f06197f [facebook] Fix test case 2014-09-29 05:19:56 +02:00
761e1645e0 [generic] Remove unstable test checksum 2014-09-29 05:18:45 +02:00
8ff14175e2 [sportdeutschland] Fix testcase 2014-09-29 05:17:16 +02:00
dbe3043cd6 [ynet] Fix test checksums 2014-09-29 05:15:42 +02:00
a8eb5a8e61 [generic] Fix testcases 2014-09-29 05:12:57 +02:00
6043f1df4e [ign] Return proper playlist object 2014-09-29 05:05:06 +02:00
12548cd933 [worldstarhiphop] Correct title extraction 2014-09-29 05:02:58 +02:00
2593039522 [vimeo] Use regexps to find description
This fixes descriptions on 2.6 and makes the code simpler.
2014-09-29 04:58:31 +02:00
35d3e63d24 release 2014.09.29.2 2014-09-29 04:49:11 +02:00
27aede9074 [pbs] Add support for series/jwplayer type video (Fixes #3849) 2014-09-29 04:48:50 +02:00
f5b7e6a842 release 2014.09.29.1 2014-09-29 02:04:28 +02:00
a1f934b171 [youtube] Correct language cookie handling 2014-09-29 02:04:16 +02:00
a43ee88c6f release 2014.09.29 2014-09-29 01:51:53 +02:00
e2dce53781 [youtube] Always request webpage in English (Fixes #3844) 2014-09-29 01:39:26 +02:00
1770ed9e86 [thvideo] Simplify (#3848) 2014-09-29 00:38:37 +02:00
457ac58cc7 Merge remote-tracking branch 'diffycat/thvideo-update' 2014-09-29 00:36:55 +02:00
9c44d2429b [vimeo:likes] Support large like lists (Fixes #3847) 2014-09-29 00:36:06 +02:00
d2e32f7df5 Do not use HTML characters in output
This messes up the format when people paste it outside of code tags.
2014-09-29 00:23:43 +02:00
67077b182b [thvideo] Add support for playlists 2014-09-28 23:36:55 +04:00
5f4c318844 [nfl] Support team micro-sites (fixes #3831) 2014-09-28 21:48:26 +03:00
dfee83234b [nfl] Prefer progressive downloads 2014-09-28 19:25:28 +03:00
7f5c0c4a19 [README] Clarify test's md5 filesize (#3846) 2014-09-28 22:10:20 +07:00
4bc77c8417 [README] Use _match_id helper function 2014-09-28 13:52:21 +02:00
22dd3fad86 release 2014.09.28.1 2014-09-28 12:14:25 +02:00
d6e6a42256 [vimeo:likes] Add new extractor (Fixes #3835) 2014-09-28 12:14:16 +02:00
76e7d1e74b [played] Remove unused import 2014-09-28 10:56:36 +02:00
38c4d41b74 [played] Simplify (#3798) 2014-09-28 10:55:27 +02:00
f0b8e3607d Merge remote-tracking branch 'r4mos/played' 2014-09-28 10:52:23 +02:00
51ee08c4bb Remove unused imports 2014-09-28 10:50:43 +02:00
c841789772 [muenchentv] Add thumbnail 2014-09-28 10:49:58 +02:00
c121a75b36 [heise] Add support for description 2014-09-28 10:49:12 +02:00
5a8b77551d [heise] Simplify (#3842) 2014-09-28 10:47:25 +02:00
0217aee154 Merge remote-tracking branch 'd912e3/heise' 2014-09-28 10:36:44 +02:00
b14f3a4c1d [golem] Simplify (#3828) 2014-09-28 10:35:19 +02:00
92f7963f6e Merge remote-tracking branch 'd912e3/golem' 2014-09-28 10:10:34 +02:00
88fbe4c2cc release 2014.09.28 2014-09-28 09:49:42 +02:00
394599f422 [oktoberfesttv] Add new extractor (Fixes #3845) 2014-09-28 09:49:21 +02:00
ed9266db90 [common] Add new helper function _match_id 2014-09-28 09:31:58 +02:00
f4b1c7adb8 [muenchentv] Move live title generation to common 2014-09-28 08:53:52 +02:00
c95eeb7b80 [eitb] Modernize 2014-09-28 08:49:03 +02:00
5e43e3803c Credit @lenaten for ynet (#3840) and sport5 (#3841) 2014-09-28 03:45:15 +07:00
a89435a7a8 [ynet] Improve _VALID_URL 2014-09-28 03:30:41 +07:00
a0a90b3ba1 Merge branch 'lenaten-ynet' 2014-09-28 03:26:47 +07:00
c664182323 [ynet] Remove unused stuff, simplify and improve 2014-09-28 03:26:38 +07:00
6be1cd4ddb Merge branch 'ynet' of https://github.com/lenaten/youtube-dl into lenaten-ynet 2014-09-28 02:56:51 +07:00
ee0d90707a [YoutubeDL] Fix string check for python3 2014-09-28 02:48:41 +07:00
f776d8f608 [sport5] Keep alphanumeric order 2014-09-28 02:35:46 +07:00
b3ac3a51ac Merge branch 'lenaten-sport5' 2014-09-28 02:32:52 +07:00
0b75c2a88b [sport5] Capture error message and improve 2014-09-28 02:31:14 +07:00
7b7518124e [heise] Don't check string type
Before Python 3 could be unicode, so don't check at all.
2014-09-27 21:12:23 +02:00
68b0973046 [YoutubeDL] Expect all kind of strings in urlopen
Now it doesn't fail if req is python2's str
2014-09-28 02:07:42 +07:00
3a203b8bfa Merge branch 'sport5' of https://github.com/lenaten/youtube-dl into lenaten-sport5 2014-09-28 00:55:16 +07:00
70752ccefd [golem] Don't omit positional argument specifiers
Required by Python 2.6.
2014-09-27 19:35:55 +02:00
0155549d6c [heise] Add new extractor 2014-09-27 19:28:01 +02:00
net
b66745288e [sport5] Add new extractor 2014-09-27 20:21:46 +03:00
net
2a1325fdde [ynet] Add new extractor 2014-09-27 20:11:22 +03:00
2f9e8776df [extremetube] Fix extraction 2014-09-27 22:36:53 +07:00
497339fa0e [anysex] Fix extraction 2014-09-27 22:29:27 +07:00
8e6f8051f0 [vbox7] Don't set the extension to 'flv' (fixes #3836) 2014-09-27 10:53:02 +02:00
11b3ce8509 [crunchyroll] Allow to list subtitles (fixes #3805) 2014-09-25 17:57:38 +03:00
6a5af6acb9 [golem] Add new extractor 2014-09-25 16:25:53 +02:00
9a0d98bb40 [vube] Update tests 2014-09-25 20:57:18 +07:00
fbd3162e49 [vube] Add DMCA notice 2014-09-25 20:48:54 +07:00
54e9a4af95 [wat] Skip test 2014-09-25 20:33:11 +07:00
8a32b82e46 [youku] Modernize somewhat 2014-09-25 09:58:09 +02:00
fec02bcc90 [hlsnative] Correct handling when remaining_bytes is None 2014-09-25 09:21:45 +02:00
c6e90caaa6 Merge remote-tracking branch 'naglis/wistia' 2014-09-25 02:03:49 +02:00
4bbf157794 release 2014.09.25 2014-09-25 01:59:45 +02:00
6b08cdf626 [youtube] Support for embedded /p players (Fixes #3821) 2014-09-25 01:59:02 +02:00
b686fc18da [hlsnative] Support test parameter 2014-09-24 14:38:40 +02:00
0b97f3a936 release 2014.09.24.1 2014-09-24 14:17:42 +02:00
eb73f2649f [vevo] Skip SMIL download 2014-09-24 14:17:33 +02:00
f0b5d6af74 [vevo] Support 1080p videos (Fixes #3656) 2014-09-24 14:16:56 +02:00
2f771f6c99 release 2014.09.24 2014-09-24 11:06:46 +02:00
3b2f933b01 [generic] Allow embedded YoutubePlaylists (Fixes #3821) 2014-09-24 11:05:14 +02:00
cc746841e7 [flickr] Modernize 2014-09-24 10:46:33 +02:00
ac7553d031 [youtube] Support embed/videoseries URLs (#3821) 2014-09-24 10:34:29 +02:00
cdc628a498 [youtube] Move more tests to extractors 2014-09-24 10:25:47 +02:00
69ea8ca42c [youtube] Remove superfluous unicode specifiers 2014-09-24 09:51:45 +02:00
4bc3a23ec5 [youtube] Modernize 2014-09-24 09:49:53 +02:00
bd5650ac64 [nfl] Fix test case - download, but don't check md5 2014-09-23 20:42:28 +03:00
86916dae4b [wat] Capture and output error message 2014-09-23 19:58:35 +07:00
f7d159cf95 [noco] Encode before passing to hashlib.md5 (Closes #3816) 2014-09-23 19:13:11 +07:00
632e5684ce [nfl] Add new extractor. (Closes #3815) 2014-09-23 00:28:19 +03:00
094d42fe44 release 2014.09.22.1 2014-09-22 18:15:07 +02:00
63cddb6477 [sbs] Recognize urls with format 'http://www.sbs.com.au/ondemand/video/<id>' (#3811) 2014-09-22 14:11:08 +02:00
273dea4248 [playfm] Fix view count and add comment count 2014-09-22 18:58:22 +07:00
f90d95edeb release 2014.09.22 2014-09-22 13:07:23 +02:00
45c85d7ba1 Merge remote-tracking branch 'origin/master' 2014-09-22 12:53:41 +02:00
d0df92928b [npo] Add extractor for tegenlicht.vpro.nl (closes #3778) 2014-09-21 16:53:00 +02:00
df8f53f752 [thvideo] Support mobile URLs as well 2014-09-21 16:32:38 +02:00
e35cb78c40 [theplatform] Correctly extract videos that don't use f4m or rtmp (reported in #3176) 2014-09-21 16:08:38 +02:00
3ef7d11acd Merge remote-tracking branch 'diffycat/thvideo' 2014-09-21 15:50:01 +02:00
224ce0d872 [nbc] Update test 2014-09-21 15:49:04 +02:00
dd41e8c82b [theplatform] Extract all formats for f4m videos 2014-09-21 15:47:58 +02:00
b509a4b176 [downloader/f4m] If <pv-2.0> is in the manifest, add it to the fragments urls query (fixes #3176)
It's used in some akamai videos (for example for theplatform.com).
2014-09-21 15:43:09 +02:00
b28c8403b2 [yourupload] Add new extractor. Fixes #3085 2014-09-21 15:13:35 +03:00
7bd4b4229a [dropbox] Recognize 'https://www.dropbox.com/sh/*' urls (fixes #3795)
And extract the title from the url last path component.
2014-09-21 13:42:08 +02:00
72e450c555 [thvideo] Add support for THVideo 2014-09-21 13:21:29 +04:00
522c55b7f2 [mgoon] Add new extractor 2014-09-21 03:26:12 +03:00
58e7071a2c [tube8] Improve _VALID_URL and add display_id 2014-09-21 06:37:11 +07:00
516812df41 Merge pull request #3801 from megustamucho/master
[tube8] Improved regex
2014-09-21 06:29:13 +07:00
752297631f [noco] Adapt to API v1.1 (Closes #3797) 2014-09-21 06:20:42 +07:00
34e14a9beb Merge remote-tracking branch 'upstream/master' 2014-09-20 22:41:45 +10:00
ffb5b05db1 Merge pull request #3800 from msmuenchen/patch-1
[divxstage] added .to TLD
2014-09-20 16:59:14 +07:00
3e8fcd9fa1 [divxstage] added .to TLD
Example video "http://www.divxstage.eu/video/930c52709d2" which gets redirected to .to TLD
2014-09-20 02:32:41 +02:00
746c67d72f [wistia] Use API and make more generic 2014-09-20 03:02:11 +03:00
5aa38e75b2 [played] Add new extractor 2014-09-19 22:46:57 +02:00
532f5bff70 [franceinter] Fix extraction and modernize 2014-09-19 20:58:50 +07:00
f566d9f1d5 release 2014.09.19 2014-09-19 09:58:01 +02:00
7267bd536f [muenchentv] Add support (Fixes #3507) 2014-09-19 09:57:53 +02:00
589d3d7c7a [moniker] rename from hypestat (#3788) 2014-09-18 21:37:09 +02:00
46f74bcf5c [soundcloud] Fix non-secret playlists 2014-09-18 18:57:04 +02:00
37bfe8ace4 [hypestat] Match URLs with www. and https:// 2014-09-18 18:56:02 +02:00
0529eef5a4 [hypestat] Unify allmyvideos and vidspot (Closes #3788) 2014-09-18 18:54:03 +02:00
fd78a4d3e6 release 2014.09.18 2014-09-18 18:43:59 +02:00
1de33fafd9 [YoutubeDL] Allow downloading multiple formats with , 2014-09-18 18:43:49 +02:00
e2e5dae64d Add -f m4a 2014-09-18 18:40:19 +02:00
09b23c902b [1up.com] Urls end now with '.html' 2014-09-18 17:02:53 +02:00
109a540e7a [ign] Fix extraction 2014-09-18 16:57:34 +02:00
2914e5f00f [drtuber] Fix categories 2014-09-18 20:56:54 +07:00
2f834e9381 [soundcloud] Secret playlists and sets
Closes #3707 again.

No test cases because I don't know what urls to use that won't be turned
into public eventually (as it happened with the first one in that
ticket)
2014-09-18 06:35:11 -03:00
9296738f20 [soundcloud] Support api urls with secret_token, Closes #3707 2014-09-18 03:02:03 -03:00
0e59b9fffb [videomega] Simplify (#3786)
* Use raw strings (r'foo') for regular expressions (enables highlighting and avoids some errors).
* title is always true-ish
2014-09-18 00:19:08 +02:00
67abbe9527 [videomega] Add new extractor. Closes #3775 2014-09-17 22:57:01 +03:00
944a3de278 [npo] Improve formats extraction (Closes #3782) 2014-09-17 19:02:57 +07:00
5a13fe9ed2 Credit @m5moufl for behindkink (#3740) 2014-09-17 12:50:47 +02:00
6b6096d0b7 release 2014.09.16.1 2014-09-16 23:35:15 +02:00
d0246d07f1 Merge remote-tracking branch 'origin/master' 2014-09-16 23:35:08 +02:00
727a98c3ee Merge remote-tracking branch 'r4mos/master' 2014-09-16 23:33:26 +02:00
997987d568 Credit @r4mos for allmyvideos 2014-09-16 23:33:13 +02:00
c001f939e4 [Allmyvideos] Fixed md5. Only 10KiB of the video file 2014-09-16 23:23:54 +02:00
e825c38082 [allmyvideos] Support multiple formats 2014-09-16 22:58:31 +02:00
a04aa7a9e6 [generic] The url in the <meta redirect> doesn't need to be enclosed in single quotes (fixes #3770)
See the examples in https://en.wikipedia.org/wiki/Meta_refresh or the shortened urls from https://t.co/.
2014-09-16 22:53:48 +02:00
7cdd5339b3 Change tabs to spaces 2014-09-16 21:05:50 +02:00
38349518f1 Added new host: allmyvideos.net 2014-09-16 20:48:53 +02:00
64892c0b79 [francetv] Adapt to new API (Closes #3751, closes #3769) 2014-09-16 23:47:59 +07:00
dc9f356846 [ard] Remove URL path escaping
This is now implemented generic in YoutubeDL.urlopen
2014-09-16 18:47:06 +07:00
ed86ee3b4a release 2014.09.16 2014-09-16 10:09:02 +02:00
7bb5df1cda [nhl] Match videos without catid (Fixes #3764) 2014-09-16 10:08:34 +02:00
37a81dff04 [behindkink] Remove call to report_extraction 2014-09-15 23:37:22 +02:00
fc96eb4e21 Merge remote-tracking branch '5moufl/behindkink' 2014-09-15 23:36:21 +02:00
ae369738b0 Credit @haricharan for einthusan (#3755) 2014-09-15 23:35:00 +02:00
e2037b3f7d [einthusan] Add description and beautify 2014-09-15 23:33:47 +02:00
5419033935 Fixed tests 2014-09-15 23:27:18 +02:00
2eebf060af Merge commit '98703c7fbfcf06348220aa63f9422cdd792cfe1a' 2014-09-15 23:26:54 +02:00
acd9db5902 Merge remote-tracking branch 'naglis/nosvideo' 2014-09-15 16:10:52 +02:00
d0e8b3d59b [nosvideo] Make more robust against missing metadata 2014-09-15 16:59:03 +03:00
c15dd15388 Merge remote-tracking branch 'naglis/turbo' 2014-09-15 15:48:48 +02:00
0003a5c416 Merge remote-tracking branch 'dstftw/escape-non-ascii-in-urls'
Conflicts:
	test/test_utils.py
2014-09-15 15:40:10 +02:00
21f2927f70 release 2014.09.15.1 2014-09-15 15:28:41 +02:00
e5a79071a5 [daum] Support non-numeric video IDs (Fixes #3749) 2014-09-15 15:25:35 +02:00
ca0e7a2b17 release 2014.09.15 2014-09-15 15:11:22 +02:00
b523bb71ab Merge remote-tracking branch 'origin/master' 2014-09-15 15:11:15 +02:00
a020a0dc20 [facebook] Fix support for untitled videos (Fixes #3757) 2014-09-15 15:10:24 +02:00
6d1f2431bd [BehindKink] Minor fixes
- fix _VALID_URL regex
 - remove unnecessary variable
 - remove second call of report_extraction
2014-09-15 15:09:17 +02:00
fdea3abdf8 [turbo] Add new extractor 2014-09-15 16:08:20 +03:00
59d284c316 [vporn] Make video URL regex more strict
There is a garbage instead of proper URL for some HD videos
2014-09-15 19:19:37 +07:00
98703c7fbf Einthusan Add new extractor 2014-09-14 23:14:00 -05:00
b04c8f7358 release 2014.09.14.3 2014-09-14 16:48:15 +02:00
56d1912f1d Add a completion script generator for the fish shell 2014-09-14 14:07:33 +02:00
eb3bd7ba8d [cloudy] Retry extraction on 410 status code (#3743 #3744) 2014-09-14 19:04:16 +07:00
2bca84e345 [BehindKink] Add new extractor 2014-09-13 17:47:19 +02:00
984e8e14ea [utils] Remove debug garbage 2014-09-13 21:08:04 +07:00
d05cfe0600 [YoutubeDL/utils] Clarify rationale for URL escaping in comment, move escape routines to utils and add some tests 2014-09-13 20:59:16 +07:00
37419b4f99 [YoutubeDL] Escape non-ASCII characters in URLs
urllib chokes on URLs with non-ASCII characters (see http://bugs.python.org/issue3991)
Working around by replacing request's original URL with escaped one
2014-09-12 23:20:17 +07:00
a8aa99442f Merge branch 'master' of https://github.com/megustamucho/youtube-dl 2014-09-09 13:07:16 +10:00
94b539d155 [tube8] Improved regex 2014-09-09 13:05:14 +10:00
b8874d4d4e [tube8] Improved regex 2014-09-09 12:48:56 +10:00
173 changed files with 6684 additions and 2152 deletions

2
.gitignore vendored
View File

@ -11,6 +11,7 @@ MANIFEST
README.txt README.txt
youtube-dl.1 youtube-dl.1
youtube-dl.bash-completion youtube-dl.bash-completion
youtube-dl.fish
youtube-dl youtube-dl
youtube-dl.exe youtube-dl.exe
youtube-dl.tar.gz youtube-dl.tar.gz
@ -29,3 +30,4 @@ updates_key.pem
*.swp *.swp
test/testdata test/testdata
.tox .tox
youtube-dl.zsh

82
AUTHORS Normal file
View File

@ -0,0 +1,82 @@
Ricardo Garcia Gonzalez
Danny Colligan
Benjamin Johnson
Vasyl' Vavrychuk
Witold Baryluk
Paweł Paprota
Gergely Imreh
Rogério Brito
Philipp Hagemeister
Sören Schulze
Kevin Ngo
Ori Avtalion
shizeeg
Filippo Valsorda
Christian Albrecht
Dave Vasilevsky
Jaime Marquínez Ferrándiz
Jeff Crouse
Osama Khalid
Michael Walter
M. Yasoob Ullah Khalid
Julien Fraichard
Johny Mo Swag
Axel Noack
Albert Kim
Pierre Rudloff
Huarong Huo
Ismael Mejía
Steffan 'Ruirize' James
Andras Elso
Jelle van der Waa
Marcin Cieślak
Anton Larionov
Takuya Tsuchida
Sergey M.
Michael Orlitzky
Chris Gahan
Saimadhav Heblikar
Mike Col
Oleg Prutz
pulpe
Andreas Schmitz
Michael Kaiser
Niklas Laxström
David Triendl
Anthony Weems
David Wagner
Juan C. Olivares
Mattias Harrysson
phaer
Sainyam Kapoor
Nicolas Évrard
Jason Normore
Hoje Lee
Adam Thalhammer
Georg Jähnig
Ralf Haring
Koki Takahashi
Ariset Llerena
Adam Malcontenti-Wilson
Tobias Bell
Naglis Jonaitis
Charles Chen
Hassaan Ali
Dobrosław Żybort
David Fabijan
Sebastian Haas
Alexander Kirk
Erik Johnson
Keith Beckman
Ole Ernst
Aaron McDaniel (mcd1992)
Magnus Kolstad
Hari Padmanaban
Carlos Ramos
5moufl
lenaten
Dennis Scheiba
Damon Timm
winwon
Xavier Beynon
Gabriel Schubiner

View File

@ -2,5 +2,6 @@ include README.md
include test/*.py include test/*.py
include test/*.json include test/*.json
include youtube-dl.bash-completion include youtube-dl.bash-completion
include youtube-dl.fish
include youtube-dl.1 include youtube-dl.1
recursive-include docs Makefile conf.py *.rst recursive-include docs Makefile conf.py *.rst

View File

@ -1,7 +1,7 @@
all: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion all: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtube-dl.fish
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 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
cleanall: clean cleanall: clean
rm -f youtube-dl youtube-dl.exe rm -f youtube-dl youtube-dl.exe
@ -9,6 +9,7 @@ cleanall: clean
PREFIX ?= /usr/local PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin BINDIR ?= $(PREFIX)/bin
MANDIR ?= $(PREFIX)/man MANDIR ?= $(PREFIX)/man
SHAREDIR ?= $(PREFIX)/share
PYTHON ?= /usr/bin/env python PYTHON ?= /usr/bin/env python
# set SYSCONFDIR to /etc if PREFIX=/usr or PREFIX=/usr/local # set SYSCONFDIR to /etc if PREFIX=/usr or PREFIX=/usr/local
@ -22,13 +23,17 @@ else
endif endif
endif endif
install: youtube-dl youtube-dl.1 youtube-dl.bash-completion install: youtube-dl youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtube-dl.fish
install -d $(DESTDIR)$(BINDIR) install -d $(DESTDIR)$(BINDIR)
install -m 755 youtube-dl $(DESTDIR)$(BINDIR) install -m 755 youtube-dl $(DESTDIR)$(BINDIR)
install -d $(DESTDIR)$(MANDIR)/man1 install -d $(DESTDIR)$(MANDIR)/man1
install -m 644 youtube-dl.1 $(DESTDIR)$(MANDIR)/man1 install -m 644 youtube-dl.1 $(DESTDIR)$(MANDIR)/man1
install -d $(DESTDIR)$(SYSCONFDIR)/bash_completion.d install -d $(DESTDIR)$(SYSCONFDIR)/bash_completion.d
install -m 644 youtube-dl.bash-completion $(DESTDIR)$(SYSCONFDIR)/bash_completion.d/youtube-dl install -m 644 youtube-dl.bash-completion $(DESTDIR)$(SYSCONFDIR)/bash_completion.d/youtube-dl
install -d $(DESTDIR)$(SHAREDIR)/zsh/site-functions
install -m 644 youtube-dl.zsh $(DESTDIR)$(SHAREDIR)/zsh/site-functions/_youtube-dl
install -d $(DESTDIR)$(SYSCONFDIR)/fish/completions
install -m 644 youtube-dl.fish $(DESTDIR)$(SYSCONFDIR)/fish/completions/youtube-dl.fish
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
@ -36,9 +41,9 @@ test:
tar: youtube-dl.tar.gz tar: youtube-dl.tar.gz
.PHONY: all clean install test tar bash-completion pypi-files .PHONY: all clean install test tar bash-completion pypi-files zsh-completion fish-completion
pypi-files: youtube-dl.bash-completion README.txt youtube-dl.1 pypi-files: youtube-dl.bash-completion README.txt youtube-dl.1 youtube-dl.fish
youtube-dl: youtube_dl/*.py youtube_dl/*/*.py youtube-dl: youtube_dl/*.py youtube_dl/*/*.py
zip --quiet youtube-dl youtube_dl/*.py youtube_dl/*/*.py zip --quiet youtube-dl youtube_dl/*.py youtube_dl/*/*.py
@ -64,7 +69,17 @@ youtube-dl.bash-completion: youtube_dl/*.py youtube_dl/*/*.py devscripts/bash-co
bash-completion: youtube-dl.bash-completion bash-completion: youtube-dl.bash-completion
youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh: youtube_dl/*.py youtube_dl/*/*.py devscripts/zsh-completion.in
python devscripts/zsh-completion.py
zsh-completion: youtube-dl.zsh
youtube-dl.fish: youtube_dl/*.py youtube_dl/*/*.py devscripts/fish-completion.in
python devscripts/fish-completion.py
fish-completion: youtube-dl.fish
youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtube-dl.fish
@tar -czf youtube-dl.tar.gz --transform "s|^|youtube-dl/|" --owner 0 --group 0 \ @tar -czf youtube-dl.tar.gz --transform "s|^|youtube-dl/|" --owner 0 --group 0 \
--exclude '*.DS_Store' \ --exclude '*.DS_Store' \
--exclude '*.kate-swp' \ --exclude '*.kate-swp' \
@ -78,5 +93,6 @@ youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-
-- \ -- \
bin devscripts test youtube_dl docs \ bin devscripts test youtube_dl docs \
LICENSE README.md README.txt \ LICENSE README.md README.txt \
Makefile MANIFEST.in youtube-dl.1 youtube-dl.bash-completion setup.py \ Makefile MANIFEST.in youtube-dl.1 youtube-dl.bash-completion \
youtube-dl.zsh youtube-dl.fish setup.py \
youtube-dl youtube-dl

View File

@ -69,6 +69,8 @@ which means you can modify it, redistribute it or use it however you like.
configuration in ~/.config/youtube-dl.conf configuration in ~/.config/youtube-dl.conf
(%APPDATA%/youtube-dl/config.txt on (%APPDATA%/youtube-dl/config.txt on
Windows) 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)
@ -99,8 +101,6 @@ which means you can modify it, redistribute it or use it however you like.
downloaded videos in it. downloaded videos in it.
--include-ads Download advertisements as well --include-ads Download advertisements as well
(experimental) (experimental)
--youtube-include-dash-manifest Try to download the DASH manifest on
YouTube videos (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
@ -158,7 +158,8 @@ which means you can modify it, redistribute it or use it however you like.
downloads if possible. downloads if possible.
--no-continue do not resume partially downloaded files --no-continue do not resume partially downloaded files
(restart from beginning) (restart from beginning)
--no-part do not use .part files --no-part do not use .part files - write directly
into output file
--no-mtime do not use the Last-modified header to set --no-mtime do not use the Last-modified header to set
the file modification time the file modification time
--write-description write video description to a .description --write-description write video description to a .description
@ -198,6 +199,10 @@ which means you can modify it, redistribute it or use it however you like.
-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 See --output for a description of available
keys. keys.
-J, --dump-single-json simulate, quiet but print JSON information
for each command-line argument. If the URL
refers to a playlist, dump the whole
playlist 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
@ -216,7 +221,7 @@ which means you can modify it, redistribute it or use it however you like.
information about the video. (Currently information about the video. (Currently
supported only for YouTube) supported only for YouTube)
--user-agent UA specify a custom user agent --user-agent UA specify a custom user agent
--referer REF 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, --add-header FIELD:VALUE specify a custom HTTP header and its value,
separated by a colon ':'. You can use this separated by a colon ':'. You can use this
@ -227,17 +232,22 @@ which means you can modify it, redistribute it or use it however you like.
## 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: "-f 22/17/18". preference using slashes: -f 22/17/18 . -f
"-f mp4" and "-f flv" are also supported. mp4 , -f m4a and -f flv are also
You can also use the special names "best", supported. You can also use the special
"bestvideo", "bestaudio", "worst", names "best", "bestvideo", "bestaudio",
"worstvideo" and "worstaudio". By default, "worst", "worstvideo" and "worstaudio". By
youtube-dl will pick the best quality. default, youtube-dl will pick the best
quality. Use commas to download multiple
audio formats, such as -f
136/137/mp4/bestvideo,140/m4a/bestaudio
--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 videos
## Subtitle Options: ## Subtitle Options:
--write-sub write subtitle file --write-sub write subtitle file
@ -253,7 +263,7 @@ which means you can modify it, redistribute it or use it however you like.
language tags like 'en,pt' language tags like 'en,pt'
## Authentication Options: ## Authentication Options:
-u, --username USERNAME account username -u, --username USERNAME login with this account ID
-p, --password PASSWORD account password -p, --password PASSWORD account password
-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
@ -264,7 +274,7 @@ which means you can modify it, redistribute it or use it however you like.
(requires ffmpeg or avconv and ffprobe or (requires ffmpeg or avconv and ffprobe or
avprobe) avprobe)
--audio-format FORMAT "best", "aac", "vorbis", "mp3", "m4a", --audio-format FORMAT "best", "aac", "vorbis", "mp3", "m4a",
"opus", or "wav"; best by default "opus", or "wav"; "best" by default
--audio-quality QUALITY ffmpeg/avconv audio quality specification, --audio-quality QUALITY ffmpeg/avconv audio quality specification,
insert a value between 0 (better) and 9 insert a value between 0 (better) and 9
(worse) for VBR or a specific bitrate like (worse) for VBR or a specific bitrate like
@ -345,21 +355,34 @@ $ youtube-dl --dateafter 20000101 --datebefore 20091231
# FAQ # FAQ
### I'm getting an error `Unable to extract OpenGraph title` on YouTube playlists ### How do I update youtube-dl?
YouTube changed their playlist format in March 2014 and later on, so you'll need at least youtube-dl 2014.07.25 to download all YouTube videos. If you've followed [our manual installation instructions](http://rg3.github.io/youtube-dl/download.html), you can simply run `youtube-dl -U` (or, on Linux, `sudo youtube-dl -U`).
If you have installed youtube-dl with a package manager, pip, setup.py or a tarball, please use that to update. Note that Ubuntu packages do not seem to get updated anymore. Since we are not affiliated with Ubuntu, there is little we can do. Feel free to report bugs to the Ubuntu packaging guys - all they have to do is update the package to a somewhat recent version. If you have used pip, a simple `sudo pip install -U youtube-dl` is sufficient to update.
Alternatively, uninstall the youtube-dl package and follow [our manual installation instructions](http://rg3.github.io/youtube-dl/download.html). In a pinch, this should do if you used `apt-get` before to install youtube-dl: If you have installed youtube-dl using a package manager like *apt-get* or *yum*, use the standard system update mechanism to update. Note that distribution packages are often outdated. As a rule of thumb, youtube-dl releases at least once a month, and often weekly or even daily. Simply go to http://yt-dl.org/ to find out the current version. Unfortunately, there is nothing we youtube-dl developers can do if your distributions serves a really outdated version. You can (and should) complain to your distribution in their bugtracker or support forum.
As a last resort, you can also uninstall the version installed by your package manager and follow our manual installation instructions. For that, remove the distribution's package, with a line like
sudo apt-get remove -y youtube-dl
Afterwards, simply follow [our manual installation instructions](http://rg3.github.io/youtube-dl/download.html):
``` ```
sudo apt-get remove -y youtube-dl
sudo wget https://yt-dl.org/latest/youtube-dl -O /usr/local/bin/youtube-dl sudo wget https://yt-dl.org/latest/youtube-dl -O /usr/local/bin/youtube-dl
sudo chmod a+x /usr/local/bin/youtube-dl sudo chmod a+x /usr/local/bin/youtube-dl
hash -r hash -r
``` ```
Again, from then on you'll be able to update with `sudo youtube-dl -U`.
### I'm getting an error `Unable to extract OpenGraph title` on YouTube playlists
YouTube changed their playlist format in March 2014 and later on, so you'll need at least youtube-dl 2014.07.25 to download all YouTube videos.
If you have installed youtube-dl with a package manager, pip, setup.py or a tarball, please use that to update. Note that Ubuntu packages do not seem to get updated anymore. Since we are not affiliated with Ubuntu, there is little we can do. Feel free to [report bugs](https://bugs.launchpad.net/ubuntu/+source/youtube-dl/+filebug) to the [Ubuntu packaging guys](mailto:ubuntu-motu@lists.ubuntu.com?subject=outdated%20version%20of%20youtube-dl) - all they have to do is update the package to a somewhat recent version. See above for a way to update.
### Do I always have to pass in `--max-quality FORMAT`, or `-citw`? ### Do I always have to pass in `--max-quality FORMAT`, or `-citw`?
By default, youtube-dl intends to have the best options (incidentally, if you have a convincing case that these should be different, [please file an issue where you explain that](https://yt-dl.org/bug)). Therefore, it is unnecessary and sometimes harmful to copy long option strings from webpages. In particular, `--max-quality` *limits* the video quality (so if you want the best quality, do NOT pass it in), and the only option out of `-citw` that is regularly useful is `-i`. By default, youtube-dl intends to have the best options (incidentally, if you have a convincing case that these should be different, [please file an issue where you explain that](https://yt-dl.org/bug)). Therefore, it is unnecessary and sometimes harmful to copy long option strings from webpages. In particular, `--max-quality` *limits* the video quality (so if you want the best quality, do NOT pass it in), and the only option out of `-citw` that is regularly useful is `-i`.
@ -439,8 +462,6 @@ If you want to add support for a new site, you can follow this quick list (assum
# coding: utf-8 # coding: utf-8
from __future__ import unicode_literals from __future__ import unicode_literals
import re
from .common import InfoExtractor from .common import InfoExtractor
@ -448,7 +469,7 @@ If you want to add support for a new site, you can follow this quick list (assum
_VALID_URL = r'https?://(?:www\.)?yourextractor\.com/watch/(?P<id>[0-9]+)' _VALID_URL = r'https?://(?:www\.)?yourextractor\.com/watch/(?P<id>[0-9]+)'
_TEST = { _TEST = {
'url': 'http://yourextractor.com/watch/42', 'url': 'http://yourextractor.com/watch/42',
'md5': 'TODO: md5 sum of the first 10KiB of the video file', 'md5': 'TODO: md5 sum of the first 10241 bytes of the video file (use --test)',
'info_dict': { 'info_dict': {
'id': '42', 'id': '42',
'ext': 'mp4', 'ext': 'mp4',
@ -463,8 +484,7 @@ If you want to add support for a new site, you can follow this quick list (assum
} }
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')
# TODO more code goes here, for example ... # TODO more code goes here, for example ...
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
@ -491,6 +511,20 @@ If you want to add support for a new site, you can follow this quick list (assum
In any case, thank you very much for your contributions! In any case, thank you very much for your contributions!
# EMBEDDING YOUTUBE-DL
youtube-dl makes the best effort to be a good command-line program, and thus should be callable from any programming language. If you encounter any problems parsing its output, feel free to [create a report](https://github.com/rg3/youtube-dl/issues/new).
From a Python program, you can embed youtube-dl in a more powerful fashion, like this:
import youtube_dl
ydl_opts = {}
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
ydl.download(['http://www.youtube.com/watch?v=BaW_jenozKc'])
Most likely, you'll want to use various options. For a list of what can be done, have a look at [youtube_dl/YoutubeDL.py](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/YoutubeDL.py#L69). For a start, if you want to intercept youtube-dl's output, set a `logger` object.
# BUGS # BUGS
Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues> . Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email. 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.

View File

@ -0,0 +1,5 @@
{{commands}}
complete --command youtube-dl --arguments ":ytfavorites :ytrecommended :ytsubscriptions :ytwatchlater :ythistory"

48
devscripts/fish-completion.py Executable file
View File

@ -0,0 +1,48 @@
#!/usr/bin/env python
from __future__ import unicode_literals
import optparse
import os
from os.path import dirname as dirn
import sys
sys.path.append(dirn(dirn((os.path.abspath(__file__)))))
import youtube_dl
from youtube_dl.utils import shell_quote
FISH_COMPLETION_FILE = 'youtube-dl.fish'
FISH_COMPLETION_TEMPLATE = 'devscripts/fish-completion.in'
EXTRA_ARGS = {
'recode-video': ['--arguments', 'mp4 flv ogg webm mkv', '--exclusive'],
# Options that need a file parameter
'download-archive': ['--require-parameter'],
'cookies': ['--require-parameter'],
'load-info': ['--require-parameter'],
'batch-file': ['--require-parameter'],
}
def build_completion(opt_parser):
commands = []
for group in opt_parser.option_groups:
for option in group.option_list:
long_option = option.get_opt_string().strip('-')
help_msg = shell_quote([option.help])
complete_cmd = ['complete', '--command', 'youtube-dl', '--long-option', long_option]
if option._short_opts:
complete_cmd += ['--short-option', option._short_opts[0].strip('-')]
if option.help != optparse.SUPPRESS_HELP:
complete_cmd += ['--description', option.help]
complete_cmd.extend(EXTRA_ARGS.get(long_option, []))
commands.append(shell_quote(complete_cmd))
with open(FISH_COMPLETION_TEMPLATE) as f:
template = f.read()
filled_template = template.replace('{{commands}}', '\n'.join(commands))
with open(FISH_COMPLETION_FILE, 'w') as f:
f.write(filled_template)
parser = youtube_dl.parseOpts()[0]
build_completion(parser)

View File

@ -0,0 +1,28 @@
#compdef youtube-dl
__youtube_dl() {
local curcontext="$curcontext" fileopts diropts cur prev
typeset -A opt_args
fileopts="{{fileopts}}"
diropts="{{diropts}}"
cur=$words[CURRENT]
case $cur in
:)
_arguments '*: :(::ytfavorites ::ytrecommended ::ytsubscriptions ::ytwatchlater ::ythistory)'
;;
*)
prev=$words[CURRENT-1]
if [[ ${prev} =~ ${fileopts} ]]; then
_path_files
elif [[ ${prev} =~ ${diropts} ]]; then
_path_files -/
elif [[ ${prev} == "--recode-video" ]]; then
_arguments '*: :(mp4 flv ogg webm mkv)'
else
_arguments '*: :({{flags}})'
fi
;;
esac
}
__youtube_dl

46
devscripts/zsh-completion.py Executable file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env python
import os
from os.path import dirname as dirn
import sys
sys.path.append(dirn(dirn((os.path.abspath(__file__)))))
import youtube_dl
ZSH_COMPLETION_FILE = "youtube-dl.zsh"
ZSH_COMPLETION_TEMPLATE = "devscripts/zsh-completion.in"
def build_completion(opt_parser):
opts = [opt for group in opt_parser.option_groups
for opt in group.option_list]
opts_file = [opt for opt in opts if opt.metavar == "FILE"]
opts_dir = [opt for opt in opts if opt.metavar == "DIR"]
fileopts = []
for opt in opts_file:
if opt._short_opts:
fileopts.extend(opt._short_opts)
if opt._long_opts:
fileopts.extend(opt._long_opts)
diropts = []
for opt in opts_dir:
if opt._short_opts:
diropts.extend(opt._short_opts)
if opt._long_opts:
diropts.extend(opt._long_opts)
flags = [opt.get_opt_string() for opt in opts]
with open(ZSH_COMPLETION_TEMPLATE) as f:
template = f.read()
template = template.replace("{{fileopts}}", "|".join(fileopts))
template = template.replace("{{diropts}}", "|".join(diropts))
template = template.replace("{{flags}}", " ".join(flags))
with open(ZSH_COMPLETION_FILE, "w") as f:
f.write(template)
parser = youtube_dl.parseOpts()[0]
build_completion(parser)

View File

@ -44,8 +44,8 @@ copyright = u'2014, Ricardo Garcia Gonzalez'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
import youtube_dl from youtube_dl.version import __version__
version = youtube_dl.__version__ version = __version__
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = version release = version

View File

@ -48,6 +48,7 @@ if len(sys.argv) >= 2 and sys.argv[1] == 'py2exe':
else: else:
files_spec = [ files_spec = [
('etc/bash_completion.d', ['youtube-dl.bash-completion']), ('etc/bash_completion.d', ['youtube-dl.bash-completion']),
('etc/fish/completions', ['youtube-dl.fish']),
('share/doc/youtube_dl', ['README.txt']), ('share/doc/youtube_dl', ['README.txt']),
('share/man/man1', ['youtube-dl.1']) ('share/man/man1', ['youtube-dl.1'])
] ]

View File

@ -1,3 +1,5 @@
from __future__ import unicode_literals
import errno import errno
import io import io
import hashlib import hashlib
@ -12,6 +14,7 @@ from youtube_dl import YoutubeDL
from youtube_dl.utils import ( from youtube_dl.utils import (
compat_str, compat_str,
preferredencoding, preferredencoding,
write_string,
) )
@ -40,10 +43,10 @@ def report_warning(message):
If stderr is a tty file the 'WARNING:' will be colored If stderr is a tty file the 'WARNING:' will be colored
''' '''
if sys.stderr.isatty() and os.name != 'nt': if sys.stderr.isatty() and os.name != 'nt':
_msg_header = u'\033[0;33mWARNING:\033[0m' _msg_header = '\033[0;33mWARNING:\033[0m'
else: else:
_msg_header = u'WARNING:' _msg_header = 'WARNING:'
output = u'%s %s\n' % (_msg_header, message) output = '%s %s\n' % (_msg_header, message)
if 'b' in getattr(sys.stderr, 'mode', '') or sys.version_info[0] < 3: if 'b' in getattr(sys.stderr, 'mode', '') or sys.version_info[0] < 3:
output = output.encode(preferredencoding()) output = output.encode(preferredencoding())
sys.stderr.write(output) sys.stderr.write(output)
@ -103,22 +106,22 @@ def expect_info_dict(self, expected_dict, got_dict):
self.assertTrue( self.assertTrue(
isinstance(got, compat_str), isinstance(got, compat_str),
u'Expected a %s object, but got %s for field %s' % ( 'Expected a %s object, but got %s for field %s' % (
compat_str.__name__, type(got).__name__, info_field)) compat_str.__name__, type(got).__name__, info_field))
self.assertTrue( self.assertTrue(
match_rex.match(got), match_rex.match(got),
u'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, 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),
u'Expected type %r for field %s, but got value %r of type %r' % (expected, info_field, got, type(got))) 'Expected type %r for field %s, but got value %r of type %r' % (expected, info_field, got, type(got)))
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))
else: else:
got = got_dict.get(info_field) got = got_dict.get(info_field)
self.assertEqual(expected, got, self.assertEqual(expected, got,
u'invalid value for field %s, expected %r, got %r' % (info_field, expected, got)) 'invalid value for field %s, expected %r, got %r' % (info_field, expected, got))
# Check for the presence of mandatory fields # Check for the presence of mandatory fields
if got_dict.get('_type') != 'playlist': if got_dict.get('_type') != 'playlist':
@ -126,7 +129,7 @@ def expect_info_dict(self, expected_dict, got_dict):
self.assertTrue(got_dict.get(key), 'Missing mandatory field %s' % key) self.assertTrue(got_dict.get(key), 'Missing mandatory field %s' % key)
# Check for mandatory fields that are automatically set by YoutubeDL # Check for mandatory fields that are automatically set by YoutubeDL
for key in ['webpage_url', 'extractor', 'extractor_key']: for key in ['webpage_url', 'extractor', 'extractor_key']:
self.assertTrue(got_dict.get(key), u'Missing field: %s' % key) self.assertTrue(got_dict.get(key), 'Missing field: %s' % key)
# 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))
@ -134,7 +137,15 @@ def expect_info_dict(self, expected_dict, got_dict):
if value and key in ('title', 'description', 'uploader', 'upload_date', 'timestamp', 'uploader_id', 'location')) if value and key in ('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:
sys.stderr.write(u'\n"info_dict": ' + json.dumps(test_info_dict, ensure_ascii=False, indent=4) + u'\n') def _repr(v):
if isinstance(v, compat_str):
return "'%s'" % v.replace('\\', '\\\\').replace("'", "\\'")
else:
return repr(v)
info_dict_str = ''.join(
' %s: %s,\n' % (_repr(k), _repr(v))
for k, v in test_info_dict.items())
write_string('\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' % (
@ -160,3 +171,13 @@ def assertGreaterEqual(self, got, expected, msg=None):
if msg is None: if msg is None:
msg = '%r not greater than or equal to %r' % (got, expected) msg = '%r not greater than or equal to %r' % (got, expected)
self.assertTrue(got >= expected, msg) self.assertTrue(got >= expected, msg)
def expect_warnings(ydl, warnings_re):
real_warning = ydl.report_warning
def _report_warning(w):
if not any(re.search(w_re, w) for w_re in warnings_re):
real_warning(w)
ydl.report_warning = _report_warning

View File

@ -14,7 +14,7 @@ from test.helper import gettestcases
from youtube_dl.extractor import ( from youtube_dl.extractor import (
FacebookIE, FacebookIE,
gen_extractors, gen_extractors,
JustinTVIE, TwitchIE,
YoutubeIE, YoutubeIE,
) )
@ -72,21 +72,17 @@ 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_justin_tv_channelid_matching(self): def test_twitch_channelid_matching(self):
self.assertTrue(JustinTVIE.suitable('justin.tv/vanillatv')) self.assertTrue(TwitchIE.suitable('twitch.tv/vanillatv'))
self.assertTrue(JustinTVIE.suitable('twitch.tv/vanillatv')) self.assertTrue(TwitchIE.suitable('www.twitch.tv/vanillatv'))
self.assertTrue(JustinTVIE.suitable('www.justin.tv/vanillatv')) self.assertTrue(TwitchIE.suitable('http://www.twitch.tv/vanillatv'))
self.assertTrue(JustinTVIE.suitable('www.twitch.tv/vanillatv')) self.assertTrue(TwitchIE.suitable('http://www.twitch.tv/vanillatv/'))
self.assertTrue(JustinTVIE.suitable('http://www.justin.tv/vanillatv'))
self.assertTrue(JustinTVIE.suitable('http://www.twitch.tv/vanillatv'))
self.assertTrue(JustinTVIE.suitable('http://www.justin.tv/vanillatv/'))
self.assertTrue(JustinTVIE.suitable('http://www.twitch.tv/vanillatv/'))
def test_justintv_videoid_matching(self): def test_twitch_videoid_matching(self):
self.assertTrue(JustinTVIE.suitable('http://www.twitch.tv/vanillatv/b/328087483')) self.assertTrue(TwitchIE.suitable('http://www.twitch.tv/vanillatv/b/328087483'))
def test_justin_tv_chapterid_matching(self): def test_twitch_chapterid_matching(self):
self.assertTrue(JustinTVIE.suitable('http://www.twitch.tv/tsm_theoddone/c/2349361')) 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)

View File

@ -8,6 +8,7 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from test.helper import ( from test.helper import (
assertGreaterEqual, assertGreaterEqual,
expect_warnings,
get_params, get_params,
gettestcases, gettestcases,
expect_info_dict, expect_info_dict,
@ -100,6 +101,7 @@ def generator(test_case):
if status['status'] == 'finished': if status['status'] == 'finished':
finished_hook_called.add(status['filename']) finished_hook_called.add(status['filename'])
ydl.add_progress_hook(_hook) ydl.add_progress_hook(_hook)
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 tc.get('file') or ydl.prepare_filename(tc.get('info_dict', {}))
@ -139,7 +141,9 @@ 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)
expect_info_dict(self, test_case.get('info_dict', {}), res_dict) expect_info_dict(self, test_case.get('info_dict', {}), res_dict)
if 'playlist_mincount' in test_case: if 'playlist_mincount' in test_case:
assertGreaterEqual( assertGreaterEqual(
self, self,
@ -181,14 +185,16 @@ def generator(test_case):
md5_for_file = _file_md5(tc_filename) md5_for_file = _file_md5(tc_filename)
self.assertEqual(md5_for_file, tc['md5']) self.assertEqual(md5_for_file, tc['md5'])
info_json_fn = os.path.splitext(tc_filename)[0] + '.info.json' info_json_fn = os.path.splitext(tc_filename)[0] + '.info.json'
self.assertTrue(os.path.exists(info_json_fn)) self.assertTrue(
os.path.exists(info_json_fn),
'Missing info file %s' % info_json_fn)
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, tc.get('info_dict', {}), info_dict)
finally: finally:
try_rm_tcs_files() try_rm_tcs_files()
if is_playlist and res_dict is not None: if is_playlist and res_dict is not None and res_dict.get('entries'):
# Remove all other files that may have been extracted if the # Remove all other files that may have been extracted if the
# extractor returns full results even with extract_flat # extractor returns full results even with extract_flat
res_tcs = [{'info_dict': e} for e in res_dict['entries']] res_tcs = [{'info_dict': e} for e in res_dict['entries']]

View File

@ -15,6 +15,7 @@ from youtube_dl.extractor import (
DailymotionIE, DailymotionIE,
TEDIE, TEDIE,
VimeoIE, VimeoIE,
WallaIE,
) )
@ -279,5 +280,32 @@ class TestVimeoSubtitles(BaseTestSubtitles):
self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang) self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang)
class TestWallaSubtitles(BaseTestSubtitles):
url = 'http://vod.walla.co.il/movie/2705958/the-yes-men'
IE = WallaIE
def test_list_subtitles(self):
self.DL.expect_warning(u'Automatic Captions not supported by this server')
self.DL.params['listsubtitles'] = True
info_dict = self.getInfoDict()
self.assertEqual(info_dict, None)
def test_allsubtitles(self):
self.DL.expect_warning(u'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(['heb']))
self.assertEqual(md5(subtitles['heb']), 'e758c5d7cb982f6bef14f377ec7a3920')
def test_nosubtitles(self):
self.DL.expect_warning(u'video doesn\'t have subtitles')
self.url = 'http://vod.walla.co.il/movie/2642630/one-direction-all-for-one'
self.DL.params['writesubtitles'] = True
self.DL.params['allsubtitles'] = True
subtitles = self.getSubtitles()
self.assertEqual(len(subtitles), 0)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -22,7 +22,8 @@ from youtube_dl.utils import (
fix_xml_ampersands, fix_xml_ampersands,
get_meta_content, get_meta_content,
orderedSet, orderedSet,
PagedList, OnDemandPagedList,
InAdvancePagedList,
parse_duration, parse_duration,
read_batch_urls, read_batch_urls,
sanitize_filename, sanitize_filename,
@ -40,6 +41,13 @@ from youtube_dl.utils import (
parse_iso8601, parse_iso8601,
strip_jsonp, strip_jsonp,
uppercase_escape, uppercase_escape,
limit_length,
escape_rfc3986,
escape_url,
js_to_json,
get_filesystem_encoding,
compat_getenv,
compat_expanduser,
) )
@ -134,6 +142,7 @@ class TestUtil(unittest.TestCase):
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('28/01/2014 21:00:00 +0100'), '20140128')
def test_find_xpath_attr(self): def test_find_xpath_attr(self):
testxml = '''<root> testxml = '''<root>
@ -243,10 +252,14 @@ class TestUtil(unittest.TestCase):
for i in range(firstid, upto): for i in range(firstid, upto):
yield i yield i
pl = PagedList(get_page, pagesize) pl = OnDemandPagedList(get_page, pagesize)
got = pl.getslice(*sliceargs) got = pl.getslice(*sliceargs)
self.assertEqual(got, expected) self.assertEqual(got, expected)
iapl = InAdvancePagedList(get_page, size // pagesize + 1, pagesize)
got = iapl.getslice(*sliceargs)
self.assertEqual(got, expected)
testPL(5, 2, (), [0, 1, 2, 3, 4]) testPL(5, 2, (), [0, 1, 2, 3, 4])
testPL(5, 2, (1,), [1, 2, 3, 4]) testPL(5, 2, (1,), [1, 2, 3, 4])
testPL(5, 2, (2,), [2, 3, 4]) testPL(5, 2, (2,), [2, 3, 4])
@ -276,6 +289,7 @@ class TestUtil(unittest.TestCase):
self.assertEqual(parse_iso8601('2014-03-23T23:04:26+0100'), 1395612266) self.assertEqual(parse_iso8601('2014-03-23T23:04:26+0100'), 1395612266)
self.assertEqual(parse_iso8601('2014-03-23T22:04:26+0000'), 1395612266) self.assertEqual(parse_iso8601('2014-03-23T22:04:26+0000'), 1395612266)
self.assertEqual(parse_iso8601('2014-03-23T22:04:26Z'), 1395612266) self.assertEqual(parse_iso8601('2014-03-23T22:04:26Z'), 1395612266)
self.assertEqual(parse_iso8601('2014-03-23T22:04:26.1234Z'), 1395612266)
def test_strip_jsonp(self): def test_strip_jsonp(self):
stripped = strip_jsonp('cb ([ {"id":"532cb",\n\n\n"x":\n3}\n]\n);') stripped = strip_jsonp('cb ([ {"id":"532cb",\n\n\n"x":\n3}\n]\n);')
@ -286,5 +300,76 @@ class TestUtil(unittest.TestCase):
self.assertEqual(uppercase_escape(''), '') self.assertEqual(uppercase_escape(''), '')
self.assertEqual(uppercase_escape('\\U0001d550'), '𝕐') self.assertEqual(uppercase_escape('\\U0001d550'), '𝕐')
def test_limit_length(self):
self.assertEqual(limit_length(None, 12), None)
self.assertEqual(limit_length('foo', 12), 'foo')
self.assertTrue(
limit_length('foo bar baz asd', 12).startswith('foo bar'))
self.assertTrue('...' in limit_length('foo bar baz asd', 12))
def test_escape_rfc3986(self):
reserved = "!*'();:@&=+$,/?#[]"
unreserved = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'
self.assertEqual(escape_rfc3986(reserved), reserved)
self.assertEqual(escape_rfc3986(unreserved), unreserved)
self.assertEqual(escape_rfc3986('тест'), '%D1%82%D0%B5%D1%81%D1%82')
self.assertEqual(escape_rfc3986('%D1%82%D0%B5%D1%81%D1%82'), '%D1%82%D0%B5%D1%81%D1%82')
self.assertEqual(escape_rfc3986('foo bar'), 'foo%20bar')
self.assertEqual(escape_rfc3986('foo%20bar'), 'foo%20bar')
def test_escape_url(self):
self.assertEqual(
escape_url('http://wowza.imust.org/srv/vod/telemb/new/UPLOAD/UPLOAD/20224_IncendieHavré_FD.mp4'),
'http://wowza.imust.org/srv/vod/telemb/new/UPLOAD/UPLOAD/20224_IncendieHavre%CC%81_FD.mp4'
)
self.assertEqual(
escape_url('http://www.ardmediathek.de/tv/Sturm-der-Liebe/Folge-2036-Zu-Mann-und-Frau-erklärt/Das-Erste/Video?documentId=22673108&bcastId=5290'),
'http://www.ardmediathek.de/tv/Sturm-der-Liebe/Folge-2036-Zu-Mann-und-Frau-erkl%C3%A4rt/Das-Erste/Video?documentId=22673108&bcastId=5290'
)
self.assertEqual(
escape_url('http://тест.рф/фрагмент'),
'http://тест.рф/%D1%84%D1%80%D0%B0%D0%B3%D0%BC%D0%B5%D0%BD%D1%82'
)
self.assertEqual(
escape_url('http://тест.рф/абв?абв=абв#абв'),
'http://тест.рф/%D0%B0%D0%B1%D0%B2?%D0%B0%D0%B1%D0%B2=%D0%B0%D0%B1%D0%B2#%D0%B0%D0%B1%D0%B2'
)
self.assertEqual(escape_url('http://vimeo.com/56015672#at=0'), 'http://vimeo.com/56015672#at=0')
def test_js_to_json_realworld(self):
inp = '''{
'clip':{'provider':'pseudo'}
}'''
self.assertEqual(js_to_json(inp), '''{
"clip":{"provider":"pseudo"}
}''')
json.loads(js_to_json(inp))
inp = '''{
'playlist':[{'controls':{'all':null}}]
}'''
self.assertEqual(js_to_json(inp), '''{
"playlist":[{"controls":{"all":null}}]
}''')
def test_js_to_json_edgecases(self):
on = js_to_json("{abc_def:'1\\'\\\\2\\\\\\'3\"4'}")
self.assertEqual(json.loads(on), {"abc_def": "1'\\2\\'3\"4"})
on = js_to_json('{"abc": true}')
self.assertEqual(json.loads(on), {'abc': True})
def test_compat_getenv(self):
test_str = 'тест'
os.environ['YOUTUBE-DL-TEST'] = (test_str if sys.version_info >= (3, 0)
else test_str.encode(get_filesystem_encoding()))
self.assertEqual(compat_getenv('YOUTUBE-DL-TEST'), test_str)
def test_compat_expanduser(self):
test_str = 'C:\Documents and Settings\тест\Application Data'
os.environ['HOME'] = (test_str if sys.version_info >= (3, 0)
else test_str.encode(get_filesystem_encoding()))
self.assertEqual(compat_expanduser('~'), test_str)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -10,7 +10,6 @@ from test.helper import FakeYDL
from youtube_dl.extractor import ( from youtube_dl.extractor import (
YoutubeUserIE,
YoutubePlaylistIE, YoutubePlaylistIE,
YoutubeIE, YoutubeIE,
YoutubeChannelIE, YoutubeChannelIE,
@ -43,28 +42,6 @@ class TestYoutubeLists(unittest.TestCase):
self.assertEqual(len(entries), 25) self.assertEqual(len(entries), 25)
self.assertEqual(YoutubeIE().extract_id(entries[-1]['url']), 'rYefUsYuEp0') self.assertEqual(YoutubeIE().extract_id(entries[-1]['url']), 'rYefUsYuEp0')
def test_youtube_channel(self):
dl = FakeYDL()
ie = YoutubeChannelIE(dl)
#test paginated channel
result = ie.extract('https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w')
self.assertTrue(len(result['entries']) > 90)
#test autogenerated channel
result = ie.extract('https://www.youtube.com/channel/HCtnHdj3df7iM/videos')
self.assertTrue(len(result['entries']) >= 18)
def test_youtube_user(self):
dl = FakeYDL()
ie = YoutubeUserIE(dl)
result = ie.extract('https://www.youtube.com/user/TheLinuxFoundation')
self.assertTrue(len(result['entries']) >= 320)
def test_youtube_show(self):
dl = FakeYDL()
ie = YoutubeShowIE(dl)
result = ie.extract('http://www.youtube.com/show/airdisasters')
self.assertTrue(len(result) >= 3)
def test_youtube_mix(self): def test_youtube_mix(self):
dl = FakeYDL() dl = FakeYDL()
ie = YoutubePlaylistIE(dl) ie = YoutubePlaylistIE(dl)
@ -83,21 +60,5 @@ class TestYoutubeLists(unittest.TestCase):
entries = result['entries'] entries = result['entries']
self.assertEqual(len(entries), 100) self.assertEqual(len(entries), 100)
def test_youtube_toplist(self):
dl = FakeYDL()
ie = YoutubeTopListIE(dl)
result = ie.extract('yttoplist:music:Trending')
entries = result['entries']
self.assertTrue(len(entries) >= 5)
def test_youtube_search_url(self):
dl = FakeYDL()
ie = YoutubeSearchURLIE(dl)
result = ie.extract('https://www.youtube.com/results?baz=bar&search_query=youtube-dl+test+video&filters=video&lclk=video')
entries = result['entries']
self.assertIsPlaylist(result)
self.assertEqual(result['title'], 'youtube-dl test video')
self.assertTrue(len(entries) >= 5)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -47,18 +47,6 @@ _TESTS = [
'2ACFC7A61CA478CD21425E5A57EBD73DDC78E22A.2094302436B2D377D14A3BBA23022D023B8BC25AA', '2ACFC7A61CA478CD21425E5A57EBD73DDC78E22A.2094302436B2D377D14A3BBA23022D023B8BC25AA',
'A52CB8B320D22032ABB3A41D773D2B6342034902.A22E87CDD37DBE75A5E52412DC874AC16A7CFCA2', 'A52CB8B320D22032ABB3A41D773D2B6342034902.A22E87CDD37DBE75A5E52412DC874AC16A7CFCA2',
), ),
(
'http://s.ytimg.com/yts/swfbin/player-vfl5vIhK2/watch_as3.swf',
'swf',
86,
'O1I3456789abcde0ghijklmnopqrstuvwxyzABCDEFGHfJKLMN2PQRSTUVWXY\\!"#$%&\'()*+,-./:;<=>?'
),
(
'http://s.ytimg.com/yts/swfbin/player-vflmDyk47/watch_as3.swf',
'swf',
'F375F75BF2AFDAAF2666E43868D46816F83F13E81C46.3725A8218E446A0DECD33F79DC282994D6AA92C92C9',
'9C29AA6D499282CD97F33DCED0A644E8128A5273.64C18E31F38361864D86834E6662FAADFA2FB57F'
),
( (
'https://s.ytimg.com/yts/jsbin/html5player-en_US-vflBb0OQx.js', 'https://s.ytimg.com/yts/jsbin/html5player-en_US-vflBb0OQx.js',
'js', 'js',

View File

@ -24,10 +24,12 @@ if os.name == 'nt':
from .utils import ( from .utils import (
compat_cookiejar, compat_cookiejar,
compat_expanduser,
compat_http_client, compat_http_client,
compat_str, compat_str,
compat_urllib_error, compat_urllib_error,
compat_urllib_request, compat_urllib_request,
escape_url,
ContentTooShortError, ContentTooShortError,
date_from_str, date_from_str,
DateRange, DateRange,
@ -60,7 +62,7 @@ from .utils import (
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 .postprocessor import FFmpegMergerPP from .postprocessor import FFmpegMergerPP, FFmpegPostProcessor
from .version import __version__ from .version import __version__
@ -106,6 +108,8 @@ class YoutubeDL(object):
forcefilename: Force printing final filename. forcefilename: Force printing final filename.
forceduration: Force printing duration. forceduration: Force printing duration.
forcejson: Force printing info_dict as JSON. forcejson: Force printing info_dict as JSON.
dump_single_json: Force printing the info_dict of the whole playlist
(or video) as a single JSON line.
simulate: Do not download the video files. simulate: Do not download the video files.
format: Video format code. format: Video format code.
format_limit: Highest quality format to try. format_limit: Highest quality format to try.
@ -164,6 +168,8 @@ class YoutubeDL(object):
'auto' for elaborate guessing 'auto' for elaborate guessing
encoding: Use this encoding instead of the system-specified. encoding: Use this encoding instead of the system-specified.
extract_flat: Do not resolve URLs, return the immediate result. extract_flat: Do not resolve URLs, return the immediate result.
Pass in 'in_playlist' to only show this behavior for
playlist items.
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 FileDownloader:
@ -183,7 +189,7 @@ class YoutubeDL(object):
_num_downloads = None _num_downloads = None
_screen_file = None _screen_file = None
def __init__(self, params=None): def __init__(self, params=None, auto_init=True):
"""Create a FileDownloader object with the given options.""" """Create a FileDownloader object with the given options."""
if params is None: if params is None:
params = {} params = {}
@ -227,11 +233,11 @@ class YoutubeDL(object):
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 not params['restrictfilenames']): and 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 '
'cannot encode all charactes. ' 'cannot encode all characters. '
'Set the LC_ALL environment variable to fix this.') 'Set the LC_ALL environment variable to fix this.')
self.params['restrictfilenames'] = True self.params['restrictfilenames'] = True
@ -240,6 +246,10 @@ class YoutubeDL(object):
self._setup_opener() self._setup_opener()
if auto_init:
self.print_debug_header()
self.add_default_info_extractors()
def add_info_extractor(self, ie): def add_info_extractor(self, ie):
"""Add an InfoExtractor object to the end of the list.""" """Add an InfoExtractor object to the end of the list."""
self._ies.append(ie) self._ies.append(ie)
@ -446,7 +456,7 @@ class YoutubeDL(object):
template_dict = collections.defaultdict(lambda: 'NA', template_dict) template_dict = collections.defaultdict(lambda: 'NA', template_dict)
outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL) outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
tmpl = os.path.expanduser(outtmpl) tmpl = compat_expanduser(outtmpl)
filename = tmpl % template_dict filename = tmpl % template_dict
return filename return filename
except ValueError as err: except ValueError as err:
@ -567,8 +577,12 @@ class YoutubeDL(object):
result_type = ie_result.get('_type', 'video') result_type = ie_result.get('_type', 'video')
if self.params.get('extract_flat', False): if result_type in ('url', 'url_transparent'):
if result_type in ('url', 'url_transparent'): extract_flat = self.params.get('extract_flat', False)
if ((extract_flat == 'in_playlist' and 'playlist' in extra_info) or
extract_flat is True):
if self.params.get('forcejson', False):
self.to_stdout(json.dumps(ie_result))
return ie_result return ie_result
if result_type == 'video': if result_type == 'video':
@ -707,7 +721,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'] extensions = ['mp4', 'flv', 'webm', '3gp', 'm4a']
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:
@ -808,28 +822,29 @@ class YoutubeDL(object):
if req_format in ('-1', 'all'): if req_format in ('-1', 'all'):
formats_to_download = formats formats_to_download = formats
else: else:
# We can accept formats requested in the format: 34/5/best, we pick for rfstr in req_format.split(','):
# the first that is available, starting from left # We can accept formats requested in the format: 34/5/best, we pick
req_formats = req_format.split('/') # the first that is available, starting from left
for rf in req_formats: req_formats = rfstr.split('/')
if re.match(r'.+?\+.+?', rf) is not None: for rf in req_formats:
# Two formats have been requested like '137+139' if re.match(r'.+?\+.+?', rf) is not None:
format_1, format_2 = rf.split('+') # Two formats have been requested like '137+139'
formats_info = (self.select_format(format_1, formats), format_1, format_2 = rf.split('+')
self.select_format(format_2, formats)) formats_info = (self.select_format(format_1, formats),
if all(formats_info): self.select_format(format_2, formats))
selected_format = { if all(formats_info):
'requested_formats': formats_info, selected_format = {
'format': rf, 'requested_formats': formats_info,
'ext': formats_info[0]['ext'], 'format': rf,
} 'ext': formats_info[0]['ext'],
}
else:
selected_format = None
else: else:
selected_format = None selected_format = self.select_format(rf, formats)
else: if selected_format is not None:
selected_format = self.select_format(rf, formats) formats_to_download.append(selected_format)
if selected_format is not None: break
formats_to_download = [selected_format]
break
if not formats_to_download: if not formats_to_download:
raise ExtractorError('requested format not available', raise ExtractorError('requested format not available',
expected=True) expected=True)
@ -895,6 +910,8 @@ class YoutubeDL(object):
if self.params.get('forcejson', False): if self.params.get('forcejson', False):
info_dict['_filename'] = filename 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):
@ -1013,7 +1030,7 @@ class YoutubeDL(object):
downloaded = [] downloaded = []
success = True success = True
merger = FFmpegMergerPP(self, not self.params.get('keepvideo')) merger = FFmpegMergerPP(self, not self.params.get('keepvideo'))
if not merger._get_executable(): if not merger._executable:
postprocessors = [] postprocessors = []
self.report_warning('You have requested multiple ' self.report_warning('You have requested multiple '
'formats but ffmpeg or avconv are not installed.' 'formats but ffmpeg or avconv are not installed.'
@ -1062,12 +1079,15 @@ class YoutubeDL(object):
for url in url_list: for url in url_list:
try: try:
#It also downloads the videos #It also downloads the videos
self.extract_info(url) res = self.extract_info(url)
except UnavailableVideoError: except UnavailableVideoError:
self.report_error('unable to download video') self.report_error('unable to download video')
except MaxDownloadsReached: except MaxDownloadsReached:
self.to_screen('[info] Maximum number of downloaded files reached.') self.to_screen('[info] Maximum number of downloaded files reached.')
raise raise
else:
if self.params.get('dump_single_json', False):
self.to_stdout(json.dumps(res))
return self._download_retcode return self._download_retcode
@ -1191,6 +1211,8 @@ class YoutubeDL(object):
res += 'video@' res += 'video@'
if fdict.get('vbr') is not None: if fdict.get('vbr') is not None:
res += '%4dk' % fdict['vbr'] res += '%4dk' % fdict['vbr']
if fdict.get('fps') is not None:
res += ', %sfps' % fdict['fps']
if fdict.get('acodec') is not None: if fdict.get('acodec') is not None:
if res: if res:
res += ', ' res += ', '
@ -1241,6 +1263,26 @@ class YoutubeDL(object):
def urlopen(self, req): def urlopen(self, req):
""" Start an HTTP download """ """ Start an HTTP download """
# According to RFC 3986, URLs can not contain non-ASCII characters, however this is not
# always respected by websites, some tend to give out URLs with non percent-encoded
# non-ASCII characters (see telemb.py, ard.py [#3412])
# 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
# percent-encoded one
req_is_string = isinstance(req, basestring if sys.version_info < (3, 0) else compat_str)
url = req if req_is_string else req.get_full_url()
url_escaped = escape_url(url)
# Substitute URL if any change after escaping
if url != url_escaped:
if req_is_string:
req = url_escaped
else:
req = compat_urllib_request.Request(
url_escaped, data=req.data, headers=req.headers,
origin_req_host=req.origin_req_host, unverifiable=req.unverifiable)
return self._opener.open(req, timeout=self._socket_timeout) return self._opener.open(req, timeout=self._socket_timeout)
def print_debug_header(self): def print_debug_header(self):
@ -1275,8 +1317,18 @@ class YoutubeDL(object):
sys.exc_clear() sys.exc_clear()
except: except:
pass pass
self._write_string('[debug] Python version %s - %s' % self._write_string('[debug] Python version %s - %s\n' % (
(platform.python_version(), platform_name()) + '\n') platform.python_version(), platform_name()))
exe_versions = FFmpegPostProcessor.get_versions()
exe_str = ', '.join(
'%s %s' % (exe, v)
for exe, v in sorted(exe_versions.items())
if v
)
if not exe_str:
exe_str = 'none'
self._write_string('[debug] exe versions: %s\n' % exe_str)
proxy_map = {} proxy_map = {}
for handler in self._opener.handlers: for handler in self._opener.handlers:

View File

@ -1,82 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__authors__ = (
'Ricardo Garcia Gonzalez',
'Danny Colligan',
'Benjamin Johnson',
'Vasyl\' Vavrychuk',
'Witold Baryluk',
'Paweł Paprota',
'Gergely Imreh',
'Rogério Brito',
'Philipp Hagemeister',
'Sören Schulze',
'Kevin Ngo',
'Ori Avtalion',
'shizeeg',
'Filippo Valsorda',
'Christian Albrecht',
'Dave Vasilevsky',
'Jaime Marquínez Ferrándiz',
'Jeff Crouse',
'Osama Khalid',
'Michael Walter',
'M. Yasoob Ullah Khalid',
'Julien Fraichard',
'Johny Mo Swag',
'Axel Noack',
'Albert Kim',
'Pierre Rudloff',
'Huarong Huo',
'Ismael Mejía',
'Steffan \'Ruirize\' James',
'Andras Elso',
'Jelle van der Waa',
'Marcin Cieślak',
'Anton Larionov',
'Takuya Tsuchida',
'Sergey M.',
'Michael Orlitzky',
'Chris Gahan',
'Saimadhav Heblikar',
'Mike Col',
'Oleg Prutz',
'pulpe',
'Andreas Schmitz',
'Michael Kaiser',
'Niklas Laxström',
'David Triendl',
'Anthony Weems',
'David Wagner',
'Juan C. Olivares',
'Mattias Harrysson',
'phaer',
'Sainyam Kapoor',
'Nicolas Évrard',
'Jason Normore',
'Hoje Lee',
'Adam Thalhammer',
'Georg Jähnig',
'Ralf Haring',
'Koki Takahashi',
'Ariset Llerena',
'Adam Malcontenti-Wilson',
'Tobias Bell',
'Naglis Jonaitis',
'Charles Chen',
'Hassaan Ali',
'Dobrosław Żybort',
'David Fabijan',
'Sebastian Haas',
'Alexander Kirk',
'Erik Johnson',
'Keith Beckman',
'Ole Ernst',
'Aaron McDaniel (mcd1992)',
'Magnus Kolstad',
)
__license__ = 'Public Domain' __license__ = 'Public Domain'
import codecs import codecs
@ -90,6 +14,7 @@ from .options import (
parseOpts, parseOpts,
) )
from .utils import ( from .utils import (
compat_expanduser,
compat_getpass, compat_getpass,
compat_print, compat_print,
DateRange, DateRange,
@ -251,8 +176,6 @@ def _real_main(argv=None):
date = DateRange.day(opts.date) date = DateRange.day(opts.date)
else: else:
date = DateRange(opts.dateafter, opts.datebefore) date = DateRange(opts.dateafter, opts.datebefore)
if opts.default_search not in ('auto', 'auto_warning', 'error', 'fixup_error', None) and ':' not in opts.default_search:
parser.error(u'--default-search invalid; did you forget a colon (:) at the end?')
# Do not download videos when there are audio-only formats # Do not download videos when there are audio-only formats
if opts.extractaudio and not opts.keepvideo and opts.format is None: if opts.extractaudio and not opts.keepvideo and opts.format is None:
@ -280,8 +203,8 @@ def _real_main(argv=None):
u' file! Use "{0}.%(ext)s" instead of "{0}" as the output' u' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
u' template'.format(outtmpl)) u' 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 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
download_archive_fn = os.path.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
ydl_opts = { ydl_opts = {
'usenetrc': opts.usenetrc, 'usenetrc': opts.usenetrc,
@ -300,8 +223,9 @@ def _real_main(argv=None):
'forcefilename': opts.getfilename, 'forcefilename': opts.getfilename,
'forceformat': opts.getformat, 'forceformat': opts.getformat,
'forcejson': opts.dumpjson, 'forcejson': opts.dumpjson,
'simulate': opts.simulate, 'dump_single_json': opts.dump_single_json,
'skip_download': (opts.skip_download or opts.simulate or any_printing), 'simulate': opts.simulate or any_printing,
'skip_download': opts.skip_download,
'format': opts.format, 'format': opts.format,
'format_limit': opts.format_limit, 'format_limit': opts.format_limit,
'listformats': opts.listformats, 'listformats': opts.listformats,
@ -365,12 +289,10 @@ def _real_main(argv=None):
'youtube_include_dash_manifest': opts.youtube_include_dash_manifest, 'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
'encoding': opts.encoding, 'encoding': opts.encoding,
'exec_cmd': opts.exec_cmd, 'exec_cmd': opts.exec_cmd,
'extract_flat': opts.extract_flat,
} }
with YoutubeDL(ydl_opts) as ydl: with YoutubeDL(ydl_opts) as ydl:
ydl.print_debug_header()
ydl.add_default_info_extractors()
# PostProcessors # PostProcessors
# Add the metadata pp first, the other pps will copy it # Add the metadata pp first, the other pps will copy it
if opts.addmetadata: if opts.addmetadata:

View File

@ -9,6 +9,7 @@ import shutil
import traceback import traceback
from .utils import ( from .utils import (
compat_expanduser,
write_json_file, write_json_file,
) )
@ -22,7 +23,7 @@ class Cache(object):
if res is None: if res is None:
cache_root = os.environ.get('XDG_CACHE_HOME', '~/.cache') cache_root = os.environ.get('XDG_CACHE_HOME', '~/.cache')
res = os.path.join(cache_root, 'youtube-dl') res = os.path.join(cache_root, 'youtube-dl')
return os.path.expanduser(res) return compat_expanduser(res)
def _get_cache_fn(self, section, key, dtype): def _get_cache_fn(self, section, key, dtype):
assert re.match(r'^[a-zA-Z0-9_.-]+$', section), \ assert re.match(r'^[a-zA-Z0-9_.-]+$', section), \

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
from .common import FileDownloader from .common import FileDownloader
from .hls import HlsFD from .hls import HlsFD
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
@ -19,6 +20,8 @@ def get_suitable_downloader(info_dict):
if url.startswith('rtmp'): if url.startswith('rtmp'):
return RtmpFD return RtmpFD
if protocol == 'm3u8_native':
return NativeHlsFD
if (protocol == 'm3u8') or (protocol is None and determine_ext(url) == 'm3u8'): if (protocol == 'm3u8') or (protocol is None and determine_ext(url) == 'm3u8'):
return HlsFD return HlsFD
if url.startswith('mms') or url.startswith('rtsp'): if url.startswith('mms') or url.startswith('rtsp'):

View File

@ -42,6 +42,7 @@ class FileDownloader(object):
Subclasses of this one must re-define the real_download method. Subclasses of this one must re-define the real_download method.
""" """
_TEST_FILE_SIZE = 10241
params = None params = None
def __init__(self, ydl, params): def __init__(self, ydl, params):

View File

@ -16,6 +16,7 @@ from ..utils import (
format_bytes, format_bytes,
encodeFilename, encodeFilename,
sanitize_open, sanitize_open,
xpath_text,
) )
@ -243,14 +244,23 @@ class F4mFD(FileDownloader):
lambda f: int(f[0]) == requested_bitrate, formats))[0] lambda f: int(f[0]) == requested_bitrate, formats))[0]
base_url = compat_urlparse.urljoin(man_url, media.attrib['url']) base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
bootstrap = base64.b64decode(doc.find(_add_ns('bootstrapInfo')).text) 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) metadata = base64.b64decode(media.find(_add_ns('metadata')).text)
boot_info = read_bootstrap_info(bootstrap) boot_info = read_bootstrap_info(bootstrap)
fragments_list = build_fragments_list(boot_info) fragments_list = build_fragments_list(boot_info)
if self.params.get('test', False): if self.params.get('test', False):
# We only download the first fragment # We only download the first fragment
fragments_list = fragments_list[:1] fragments_list = fragments_list[:1]
total_frags = len(fragments_list) 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')
@ -290,6 +300,8 @@ class F4mFD(FileDownloader):
for (seg_i, frag_i) in fragments_list: for (seg_i, frag_i) in fragments_list:
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:
url += '?' + akamai_pv.strip(';')
frag_filename = '%s-%s' % (tmpfilename, name) frag_filename = '%s-%s' % (tmpfilename, name)
success = http_dl.download(frag_filename, {'url': url}) success = http_dl.download(frag_filename, {'url': url})
if not success: if not success:

View File

@ -1,8 +1,13 @@
from __future__ import unicode_literals
import os import os
import re
import subprocess import subprocess
from .common import FileDownloader from .common import FileDownloader
from ..utils import ( from ..utils import (
compat_urlparse,
compat_urllib_request,
check_executable, check_executable,
encodeFilename, encodeFilename,
) )
@ -43,3 +48,57 @@ class HlsFD(FileDownloader):
self.to_stderr(u"\n") self.to_stderr(u"\n")
self.report_error(u'%s exited with code %d' % (program, retval)) self.report_error(u'%s exited with code %d' % (program, retval))
return False return False
class NativeHlsFD(FileDownloader):
""" A more limited implementation that does not require ffmpeg """
def real_download(self, filename, info_dict):
url = info_dict['url']
self.report_destination(filename)
tmpfilename = self.temp_name(filename)
self.to_screen(
'[hlsnative] %s: Downloading m3u8 manifest' % info_dict['id'])
data = self.ydl.urlopen(url).read()
s = data.decode('utf-8', 'ignore')
segment_urls = []
for line in s.splitlines():
line = line.strip()
if line and not line.startswith('#'):
segment_url = (
line
if re.match(r'^https?://', line)
else compat_urlparse.urljoin(url, line))
segment_urls.append(segment_url)
is_test = self.params.get('test', False)
remaining_bytes = self._TEST_FILE_SIZE if is_test else None
byte_counter = 0
with open(tmpfilename, 'wb') as outf:
for i, segurl in enumerate(segment_urls):
self.to_screen(
'[hlsnative] %s: Downloading segment %d / %d' %
(info_dict['id'], i + 1, len(segment_urls)))
seg_req = compat_urllib_request.Request(segurl)
if remaining_bytes is not None:
seg_req.add_header('Range', 'bytes=0-%d' % (remaining_bytes - 1))
segment = self.ydl.urlopen(seg_req).read()
if remaining_bytes is not None:
segment = segment[:remaining_bytes]
remaining_bytes -= len(segment)
outf.write(segment)
byte_counter += len(segment)
if remaining_bytes is not None and remaining_bytes <= 0:
break
self._hook_progress({
'downloaded_bytes': byte_counter,
'total_bytes': byte_counter,
'filename': filename,
'status': 'finished',
})
self.try_rename(tmpfilename, filename)
return True

View File

@ -14,8 +14,6 @@ from ..utils import (
class HttpFD(FileDownloader): class HttpFD(FileDownloader):
_TEST_FILE_SIZE = 10241
def real_download(self, filename, info_dict): def real_download(self, filename, info_dict):
url = info_dict['url'] url = info_dict['url']
tmpfilename = self.temp_name(filename) tmpfilename = self.temp_name(filename)

View File

@ -20,11 +20,14 @@ from .arte import (
ArteTVDDCIE, ArteTVDDCIE,
ArteTVEmbedIE, ArteTVEmbedIE,
) )
from .audiomack import AudiomackIE
from .auengine import AUEngineIE from .auengine import AUEngineIE
from .bambuser import BambuserIE, BambuserChannelIE from .bambuser import BambuserIE, BambuserChannelIE
from .bandcamp import BandcampIE, BandcampAlbumIE from .bandcamp import BandcampIE, BandcampAlbumIE
from .bbccouk import BBCCoUkIE from .bbccouk import BBCCoUkIE
from .beeg import BeegIE from .beeg import BeegIE
from .behindkink import BehindKinkIE
from .bild import BildIE
from .bilibili import BiliBiliIE from .bilibili import BiliBiliIE
from .blinkx import BlinkxIE from .blinkx import BlinkxIE
from .bliptv import BlipTVIE, BlipTVUserIE from .bliptv import BlipTVIE, BlipTVUserIE
@ -59,9 +62,11 @@ from .comedycentral import ComedyCentralIE, ComedyCentralShowsIE
from .condenast import CondeNastIE from .condenast import CondeNastIE
from .cracked import CrackedIE from .cracked import CrackedIE
from .criterion import CriterionIE from .criterion import CriterionIE
from .crunchyroll import CrunchyrollIE from .crunchyroll import (
CrunchyrollIE,
CrunchyrollShowPlaylistIE
)
from .cspan import CSpanIE from .cspan import CSpanIE
from .d8 import D8IE
from .dailymotion import ( from .dailymotion import (
DailymotionIE, DailymotionIE,
DailymotionPlaylistIE, DailymotionPlaylistIE,
@ -83,6 +88,7 @@ from .dropbox import DropboxIE
from .ebaumsworld import EbaumsWorldIE from .ebaumsworld import EbaumsWorldIE
from .ehow import EHowIE from .ehow import EHowIE
from .eighttracks import EightTracksIE from .eighttracks import EightTracksIE
from .einthusan import EinthusanIE
from .eitb import EitbIE from .eitb import EitbIE
from .ellentv import ( from .ellentv import (
EllenTVIE, EllenTVIE,
@ -132,13 +138,17 @@ from .gamestar import GameStarIE
from .gametrailers import GametrailersIE from .gametrailers import GametrailersIE
from .gdcvault import GDCVaultIE from .gdcvault import GDCVaultIE
from .generic import GenericIE from .generic import GenericIE
from .glide import GlideIE
from .globo import GloboIE
from .godtube import GodTubeIE from .godtube import GodTubeIE
from .golem import GolemIE
from .googleplus import GooglePlusIE from .googleplus import GooglePlusIE
from .googlesearch import GoogleSearchIE from .googlesearch import GoogleSearchIE
from .gorillavid import GorillaVidIE from .gorillavid import GorillaVidIE
from .goshgay import GoshgayIE from .goshgay import GoshgayIE
from .grooveshark import GroovesharkIE from .grooveshark import GroovesharkIE
from .hark import HarkIE from .hark import HarkIE
from .heise import HeiseIE
from .helsinki import HelsinkiIE from .helsinki import HelsinkiIE
from .hentaistigma import HentaiStigmaIE from .hentaistigma import HentaiStigmaIE
from .hornbunny import HornBunnyIE from .hornbunny import HornBunnyIE
@ -168,7 +178,6 @@ from .jadorecettepub import JadoreCettePubIE
from .jeuxvideo import JeuxVideoIE from .jeuxvideo import JeuxVideoIE
from .jove import JoveIE from .jove import JoveIE
from .jukebox import JukeboxIE from .jukebox import JukeboxIE
from .justintv import JustinTVIE
from .jpopsukitv import JpopsukiIE from .jpopsukitv import JpopsukiIE
from .kankan import KankanIE from .kankan import KankanIE
from .keezmovies import KeezMoviesIE from .keezmovies import KeezMoviesIE
@ -179,6 +188,7 @@ from .kontrtube import KontrTubeIE
from .krasview import KrasViewIE 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 .lifenews import LifeNewsIE from .lifenews import LifeNewsIE
from .liveleak import LiveLeakIE from .liveleak import LiveLeakIE
from .livestream import ( from .livestream import (
@ -186,6 +196,7 @@ from .livestream import (
LivestreamOriginalIE, LivestreamOriginalIE,
LivestreamShortenerIE, LivestreamShortenerIE,
) )
from .lrt import LRTIE
from .lynda import ( from .lynda import (
LyndaIE, LyndaIE,
LyndaCourseIE LyndaCourseIE
@ -197,6 +208,7 @@ from .malemotion import MalemotionIE
from .mdr import MDRIE from .mdr import MDRIE
from .metacafe import MetacafeIE from .metacafe import MetacafeIE
from .metacritic import MetacriticIE from .metacritic import MetacriticIE
from .mgoon import MgoonIE
from .ministrygrid import MinistryGridIE from .ministrygrid import MinistryGridIE
from .mit import TechTVMITIE, MITIE, OCWMITIE from .mit import TechTVMITIE, MITIE, OCWMITIE
from .mitele import MiTeleIE from .mitele import MiTeleIE
@ -206,6 +218,7 @@ from .mpora import MporaIE
from .moevideo import MoeVideoIE from .moevideo import MoeVideoIE
from .mofosex import MofosexIE from .mofosex import MofosexIE
from .mojvideo import MojvideoIE from .mojvideo import MojvideoIE
from .moniker import MonikerIE
from .mooshare import MooshareIE from .mooshare import MooshareIE
from .morningstar import MorningstarIE from .morningstar import MorningstarIE
from .motherless import MotherlessIE from .motherless import MotherlessIE
@ -218,6 +231,7 @@ from .mtv import (
MTVServicesEmbeddedIE, MTVServicesEmbeddedIE,
MTVIggyIE, MTVIggyIE,
) )
from .muenchentv import MuenchenTVIE
from .musicplayon import MusicPlayOnIE from .musicplayon import MusicPlayOnIE
from .musicvault import MusicVaultIE from .musicvault import MusicVaultIE
from .muzu import MuzuTVIE from .muzu import MuzuTVIE
@ -235,8 +249,9 @@ from .ndtv import NDTVIE
from .newgrounds import NewgroundsIE from .newgrounds import NewgroundsIE
from .newstube import NewstubeIE from .newstube import NewstubeIE
from .nfb import NFBIE from .nfb import NFBIE
from .nfl import NFLIE
from .nhl import NHLIE, NHLVideocenterIE from .nhl import NHLIE, NHLVideocenterIE
from .niconico import NiconicoIE from .niconico import NiconicoIE, NiconicoPlaylistIE
from .ninegag import NineGagIE from .ninegag import NineGagIE
from .noco import NocoIE from .noco import NocoIE
from .normalboots import NormalbootsIE from .normalboots import NormalbootsIE
@ -244,7 +259,10 @@ from .nosvideo import NosVideoIE
from .novamov import NovaMovIE from .novamov import NovaMovIE
from .nowness import NownessIE from .nowness import NownessIE
from .nowvideo import NowVideoIE from .nowvideo import NowVideoIE
from .npo import NPOIE from .npo import (
NPOIE,
TegenlichtVproIE,
)
from .nrk import ( from .nrk import (
NRKIE, NRKIE,
NRKTVIE, NRKTVIE,
@ -252,6 +270,7 @@ from .nrk import (
from .ntv import NTVIE from .ntv import NTVIE
from .nytimes import NYTimesIE from .nytimes import NYTimesIE
from .nuvid import NuvidIE from .nuvid import NuvidIE
from .oktoberfesttv import OktoberfestTVIE
from .ooyala import OoyalaIE from .ooyala import OoyalaIE
from .orf import ( from .orf import (
ORFTVthekIE, ORFTVthekIE,
@ -261,7 +280,10 @@ from .orf import (
from .parliamentliveuk import ParliamentLiveUKIE from .parliamentliveuk import ParliamentLiveUKIE
from .patreon import PatreonIE from .patreon import PatreonIE
from .pbs import PBSIE from .pbs import PBSIE
from .phoenix import PhoenixIE
from .photobucket import PhotobucketIE from .photobucket import PhotobucketIE
from .planetaplay import PlanetaPlayIE
from .played import PlayedIE
from .playfm import PlayFMIE from .playfm import PlayFMIE
from .playvid import PlayvidIE from .playvid import PlayvidIE
from .podomatic import PodomaticIE from .podomatic import PodomaticIE
@ -272,6 +294,7 @@ from .pornoxo import PornoXOIE
from .promptfile import PromptFileIE from .promptfile import PromptFileIE
from .prosiebensat1 import ProSiebenSat1IE from .prosiebensat1 import ProSiebenSat1IE
from .pyvideo import PyvideoIE from .pyvideo import PyvideoIE
from .quickvid import QuickVidIE
from .radiofrance import RadioFranceIE from .radiofrance import RadioFranceIE
from .rai import RaiIE from .rai import RaiIE
from .rbmaradio import RBMARadioIE from .rbmaradio import RBMARadioIE
@ -300,6 +323,7 @@ from .sbs import SBSIE
from .scivee import SciVeeIE from .scivee import SciVeeIE
from .screencast import ScreencastIE from .screencast import ScreencastIE
from .servingsys import ServingSysIE from .servingsys import ServingSysIE
from .sexykarma import SexyKarmaIE
from .shared import SharedIE from .shared import SharedIE
from .sharesix import ShareSixIE from .sharesix import ShareSixIE
from .sina import SinaIE from .sina import SinaIE
@ -330,7 +354,10 @@ from .spankwire import SpankwireIE
from .spiegel import SpiegelIE, SpiegelArticleIE from .spiegel import SpiegelIE, SpiegelArticleIE
from .spiegeltv import SpiegeltvIE from .spiegeltv import SpiegeltvIE
from .spike import SpikeIE from .spike import SpikeIE
from .sport5 import Sport5IE
from .sportbox import SportBoxIE
from .sportdeutschland import SportDeutschlandIE from .sportdeutschland import SportDeutschlandIE
from .srmediathek import SRMediathekIE
from .stanfordoc import StanfordOpenClassroomIE from .stanfordoc import StanfordOpenClassroomIE
from .steam import SteamIE from .steam import SteamIE
from .streamcloud import StreamcloudIE from .streamcloud import StreamcloudIE
@ -340,6 +367,7 @@ from .swrmediathek import SWRMediathekIE
from .syfy import SyfyIE from .syfy import SyfyIE
from .sztvhu import SztvHuIE from .sztvhu import SztvHuIE
from .tagesschau import TagesschauIE from .tagesschau import TagesschauIE
from .tapely import TapelyIE
from .teachertube import ( from .teachertube import (
TeacherTubeIE, TeacherTubeIE,
TeacherTubeUserIE, TeacherTubeUserIE,
@ -348,15 +376,22 @@ from .teachingchannel import TeachingChannelIE
from .teamcoco import TeamcocoIE from .teamcoco import TeamcocoIE
from .techtalks import TechTalksIE from .techtalks import TechTalksIE
from .ted import TEDIE from .ted import TEDIE
from .telecinco import TelecincoIE
from .telemb import TeleMBIE from .telemb import TeleMBIE
from .tenplay import TenPlayIE from .tenplay import TenPlayIE
from .testurl import TestURLIE from .testurl import TestURLIE
from .tf1 import TF1IE from .tf1 import TF1IE
from .theonion import TheOnionIE
from .theplatform import ThePlatformIE from .theplatform import ThePlatformIE
from .thesixtyone import TheSixtyOneIE
from .thisav import ThisAVIE from .thisav import ThisAVIE
from .tinypic import TinyPicIE from .tinypic import TinyPicIE
from .tlc import TlcIE, TlcDeIE from .tlc import TlcIE, TlcDeIE
from .tnaflix import TNAFlixIE from .tnaflix import TNAFlixIE
from .thvideo import (
THVideoIE,
THVideoPlaylistIE
)
from .toutv import TouTvIE from .toutv import TouTvIE
from .toypics import ToypicsUserIE, ToypicsIE from .toypics import ToypicsUserIE, ToypicsIE
from .traileraddict import TrailerAddictIE from .traileraddict import TrailerAddictIE
@ -365,10 +400,12 @@ from .trutube import TruTubeIE
from .tube8 import Tube8IE from .tube8 import Tube8IE
from .tudou import TudouIE from .tudou import TudouIE
from .tumblr import TumblrIE from .tumblr import TumblrIE
from .turbo import TurboIE
from .tutv import TutvIE from .tutv import TutvIE
from .tvigle import TvigleIE from .tvigle import TvigleIE
from .tvp import TvpIE from .tvp import TvpIE
from .tvplay import TVPlayIE from .tvplay import TVPlayIE
from .twitch import TwitchIE
from .ubu import UbuIE from .ubu import UbuIE
from .udemy import ( from .udemy import (
UdemyIE, UdemyIE,
@ -389,17 +426,20 @@ from .videobam import VideoBamIE
from .videodetective import VideoDetectiveIE from .videodetective import VideoDetectiveIE
from .videolecturesnet import VideoLecturesNetIE from .videolecturesnet import VideoLecturesNetIE
from .videofyme import VideofyMeIE from .videofyme import VideofyMeIE
from .videomega import VideoMegaIE
from .videopremium import VideoPremiumIE from .videopremium import VideoPremiumIE
from .videott import VideoTtIE from .videott import VideoTtIE
from .videoweed import VideoWeedIE from .videoweed import VideoWeedIE
from .vidme import VidmeIE from .vidme import VidmeIE
from .vidzi import VidziIE
from .vimeo import ( from .vimeo import (
VimeoIE, VimeoIE,
VimeoChannelIE,
VimeoUserIE,
VimeoAlbumIE, VimeoAlbumIE,
VimeoChannelIE,
VimeoGroupsIE, VimeoGroupsIE,
VimeoLikesIE,
VimeoReviewIE, VimeoReviewIE,
VimeoUserIE,
VimeoWatchLaterIE, VimeoWatchLaterIE,
) )
from .vimple import VimpleIE from .vimple import VimpleIE
@ -411,9 +451,11 @@ from .viki import VikiIE
from .vk import VKIE from .vk import VKIE
from .vodlocker import VodlockerIE from .vodlocker import VodlockerIE
from .vporn import VpornIE from .vporn import VpornIE
from .vrt import VRTIE
from .vube import VubeIE from .vube import VubeIE
from .vuclip import VuClipIE from .vuclip import VuClipIE
from .vulture import VultureIE from .vulture import VultureIE
from .walla import WallaIE
from .washingtonpost import WashingtonPostIE from .washingtonpost import WashingtonPostIE
from .wat import WatIE from .wat import WatIE
from .wayofthemaster import WayOfTheMasterIE from .wayofthemaster import WayOfTheMasterIE
@ -435,12 +477,13 @@ from .xvideos import XVideosIE
from .xtube import XTubeUserIE, XTubeIE from .xtube import XTubeUserIE, XTubeIE
from .yahoo import ( from .yahoo import (
YahooIE, YahooIE,
YahooNewsIE,
YahooSearchIE, YahooSearchIE,
) )
from .ynet import YnetIE
from .youjizz import YouJizzIE from .youjizz import YouJizzIE
from .youku import YoukuIE from .youku import YoukuIE
from .youporn import YouPornIE from .youporn import YouPornIE
from .yourupload import YourUploadIE
from .youtube import ( from .youtube import (
YoutubeIE, YoutubeIE,
YoutubeChannelIE, YoutubeChannelIE,
@ -458,10 +501,8 @@ from .youtube import (
YoutubeUserIE, YoutubeUserIE,
YoutubeWatchLaterIE, YoutubeWatchLaterIE,
) )
from .zdf import ZDFIE from .zdf import ZDFIE
_ALL_CLASSES = [ _ALL_CLASSES = [
klass klass
for name, klass in globals().items() for name, klass in globals().items()

View File

@ -22,8 +22,7 @@ class ABCIE(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)
urls_info_json = self._search_regex( urls_info_json = self._search_regex(

View File

@ -35,7 +35,7 @@ class AnySexIE(InfoExtractor):
title = self._html_search_regex(r'<title>(.*?)</title>', webpage, 'title') title = self._html_search_regex(r'<title>(.*?)</title>', webpage, 'title')
description = self._html_search_regex( description = self._html_search_regex(
r'<div class="description">([^<]+)</div>', webpage, 'description', fatal=False) r'<div class="description"[^>]*>([^<]+)</div>', webpage, 'description', fatal=False)
thumbnail = self._html_search_regex( thumbnail = self._html_search_regex(
r'preview_url\s*:\s*\'(.*?)\'', webpage, 'thumbnail', fatal=False) r'preview_url\s*:\s*\'(.*?)\'', webpage, 'thumbnail', fatal=False)
@ -43,7 +43,7 @@ class AnySexIE(InfoExtractor):
r'<a href="http://anysex\.com/categories/[^"]+" title="[^"]*">([^<]+)</a>', webpage) r'<a href="http://anysex\.com/categories/[^"]+" title="[^"]*">([^<]+)</a>', webpage)
duration = parse_duration(self._search_regex( duration = parse_duration(self._search_regex(
r'<b>Duration:</b> (\d+:\d+)', webpage, 'duration', fatal=False)) r'<b>Duration:</b> (?:<q itemprop="duration">)?(\d+:\d+)', webpage, 'duration', fatal=False))
view_count = int_or_none(self._html_search_regex( view_count = int_or_none(self._html_search_regex(
r'<b>Views:</b> (\d+)', webpage, 'view count', fatal=False)) r'<b>Views:</b> (\d+)', webpage, 'view count', fatal=False))

View File

@ -4,16 +4,16 @@ from __future__ import unicode_literals
import re import re
from .common import InfoExtractor from .common import InfoExtractor
from .generic import GenericIE
from ..utils import ( from ..utils import (
determine_ext, determine_ext,
ExtractorError, ExtractorError,
qualities, qualities,
compat_urllib_parse_urlparse,
compat_urllib_parse,
int_or_none, int_or_none,
parse_duration, parse_duration,
unified_strdate, unified_strdate,
xpath_text, xpath_text,
parse_xml,
) )
@ -51,14 +51,16 @@ class ARDMediathekIE(InfoExtractor):
else: else:
video_id = m.group('video_id') video_id = m.group('video_id')
urlp = compat_urllib_parse_urlparse(url)
url = urlp._replace(path=compat_urllib_parse.quote(urlp.path.encode('utf-8'))).geturl()
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
if '>Der gewünschte Beitrag ist nicht mehr verfügbar.<' in webpage: if '>Der gewünschte Beitrag ist nicht mehr verfügbar.<' in webpage:
raise ExtractorError('Video %s is no longer available' % video_id, expected=True) raise ExtractorError('Video %s is no longer available' % video_id, expected=True)
if re.search(r'[\?&]rss($|[=&])', url):
doc = parse_xml(webpage)
if doc.tag == 'rss':
return GenericIE()._extract_rss(url, video_id, doc)
title = self._html_search_regex( title = self._html_search_regex(
[r'<h1(?:\s+class="boxTopHeadline")?>(.*?)</h1>', [r'<h1(?:\s+class="boxTopHeadline")?>(.*?)</h1>',
r'<meta name="dcterms.title" content="(.*?)"/>', r'<meta name="dcterms.title" content="(.*?)"/>',

View File

@ -10,8 +10,8 @@ from ..utils import (
unified_strdate, unified_strdate,
determine_ext, determine_ext,
get_element_by_id, get_element_by_id,
compat_str,
get_element_by_attribute, get_element_by_attribute,
int_or_none,
) )
# There are different sources of video in arte.tv, the extraction process # There are different sources of video in arte.tv, the extraction process
@ -86,15 +86,28 @@ class ArteTVPlus7IE(InfoExtractor):
info = self._download_json(json_url, video_id) info = self._download_json(json_url, video_id)
player_info = info['videoJsonPlayer'] player_info = info['videoJsonPlayer']
upload_date_str = player_info.get('shootingDate')
if not upload_date_str:
upload_date_str = player_info.get('VDA', '').split(' ')[0]
title = player_info['VTI'].strip()
subtitle = player_info.get('VSU', '').strip()
if subtitle:
title += ' - %s' % subtitle
info_dict = { info_dict = {
'id': player_info['VID'], 'id': player_info['VID'],
'title': player_info['VTI'], 'title': title,
'description': player_info.get('VDE'), 'description': player_info.get('VDE'),
'upload_date': unified_strdate(player_info.get('VDA', '').split(' ')[0]), 'upload_date': unified_strdate(upload_date_str),
'thumbnail': player_info.get('programImage') or player_info.get('VTU', {}).get('IUR'), 'thumbnail': player_info.get('programImage') or player_info.get('VTU', {}).get('IUR'),
} }
all_formats = player_info['VSR'].values() all_formats = []
for format_id, format_dict in player_info['VSR'].items():
fmt = dict(format_dict)
fmt['format_id'] = format_id
all_formats.append(fmt)
# Some formats use the m3u8 protocol # Some formats use the m3u8 protocol
all_formats = list(filter(lambda f: f.get('videoFormat') != 'M3U8', all_formats)) all_formats = list(filter(lambda f: f.get('videoFormat') != 'M3U8', all_formats))
def _match_lang(f): def _match_lang(f):
@ -145,22 +158,12 @@ class ArteTVPlus7IE(InfoExtractor):
) )
formats = sorted(formats, key=sort_key) formats = sorted(formats, key=sort_key)
def _format(format_info): def _format(format_info):
quality = ''
height = format_info.get('height')
if height is not None:
quality = compat_str(height)
bitrate = format_info.get('bitrate')
if bitrate is not None:
quality += '-%d' % bitrate
if format_info.get('versionCode') is not None:
format_id = '%s-%s' % (quality, format_info['versionCode'])
else:
format_id = quality
info = { info = {
'format_id': format_id, 'format_id': format_info['format_id'],
'format_note': format_info.get('versionLibelle'), 'format_note': '%s, %s' % (format_info.get('versionCode'), format_info.get('versionLibelle')),
'width': format_info.get('width'), 'width': int_or_none(format_info.get('width')),
'height': height, 'height': int_or_none(format_info.get('height')),
'tbr': int_or_none(format_info.get('bitrate')),
} }
if format_info['mediaType'] == 'rtmp': if format_info['mediaType'] == 'rtmp':
info['url'] = format_info['streamer'] info['url'] = format_info['streamer']

View File

@ -0,0 +1,69 @@
# coding: utf-8
from __future__ import unicode_literals
from .common import InfoExtractor
from .soundcloud import SoundcloudIE
from ..utils import ExtractorError
import time
class AudiomackIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?audiomack\.com/song/(?P<id>[\w/-]+)'
IE_NAME = 'audiomack'
_TESTS = [
#hosted on audiomack
{
'url': 'http://www.audiomack.com/song/roosh-williams/extraordinary',
'info_dict':
{
'id' : 'roosh-williams/extraordinary',
'ext': 'mp3',
'title': 'Roosh Williams - Extraordinary'
}
},
#hosted on soundcloud via audiomack
{
'url': 'http://www.audiomack.com/song/xclusiveszone/take-kare',
'file': '172419696.mp3',
'info_dict':
{
'ext': 'mp3',
'title': 'Young Thug ft Lil Wayne - Take Kare',
"upload_date": "20141016",
"description": "New track produced by London On Da Track called “Take Kare\"\n\nhttp://instagram.com/theyoungthugworld\nhttps://www.facebook.com/ThuggerThuggerCashMoney\n",
"uploader": "Young Thug World"
}
}
]
def _real_extract(self, url):
video_id = self._match_id(url)
api_response = self._download_json(
"http://www.audiomack.com/api/music/url/song/%s?_=%d" % (
video_id, time.time()),
video_id)
if "url" not in api_response:
raise ExtractorError("Unable to deduce api url of song")
realurl = api_response["url"]
#Audiomack wraps a lot of soundcloud tracks in their branded wrapper
# - if so, pass the work off to the soundcloud extractor
if SoundcloudIE.suitable(realurl):
return {'_type': 'url', 'url': realurl, '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 {
'id': video_id,
'title': title,
'url': realurl,
}

View File

@ -24,8 +24,7 @@ class AUEngineIE(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)
title = self._html_search_regex(r'<title>(?P<title>.+?)</title>', webpage, 'title') title = self._html_search_regex(r'<title>(?P<title>.+?)</title>', webpage, 'title')

View File

@ -15,13 +15,23 @@ class BandcampIE(InfoExtractor):
_VALID_URL = r'https?://.*?\.bandcamp\.com/track/(?P<title>.*)' _VALID_URL = r'https?://.*?\.bandcamp\.com/track/(?P<title>.*)'
_TESTS = [{ _TESTS = [{
'url': 'http://youtube-dl.bandcamp.com/track/youtube-dl-test-song', 'url': 'http://youtube-dl.bandcamp.com/track/youtube-dl-test-song',
'file': '1812978515.mp3',
'md5': 'c557841d5e50261777a6585648adf439', 'md5': 'c557841d5e50261777a6585648adf439',
'info_dict': { 'info_dict': {
"title": "youtube-dl \"'/\\\u00e4\u21ad - youtube-dl test song \"'/\\\u00e4\u21ad", 'id': '1812978515',
"duration": 9.8485, 'ext': 'mp3',
'title': "youtube-dl \"'/\\\u00e4\u21ad - youtube-dl test song \"'/\\\u00e4\u21ad",
'duration': 9.8485,
}, },
'_skip': 'There is a limit of 200 free downloads / month for the test song' '_skip': 'There is a limit of 200 free downloads / month for the test song'
}, {
'url': 'http://benprunty.bandcamp.com/track/lanius-battle',
'md5': '2b68e5851514c20efdff2afc5603b8b4',
'info_dict': {
'id': '2650410135',
'ext': 'mp3',
'title': 'Lanius (Battle)',
'uploader': 'Ben Prunty Music',
},
}] }]
def _real_extract(self, url): def _real_extract(self, url):
@ -59,9 +69,9 @@ class BandcampIE(InfoExtractor):
raise ExtractorError('No free songs found') raise ExtractorError('No free songs found')
download_link = m_download.group(1) download_link = m_download.group(1)
video_id = re.search( video_id = self._search_regex(
r'var TralbumData = {(.*?)id: (?P<id>\d*?)$', r'var TralbumData = {.*?id: (?P<id>\d+),?$',
webpage, re.MULTILINE | re.DOTALL).group('id') webpage, 'video id', flags=re.MULTILINE | re.DOTALL)
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

View File

@ -0,0 +1,53 @@
# coding: utf-8
from __future__ import unicode_literals
import re
from .common import InfoExtractor
from ..utils import url_basename
class BehindKinkIE(InfoExtractor):
_VALID_URL = r'http://(?:www\.)?behindkink\.com/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/(?P<id>[^/#?_]+)'
_TEST = {
'url': 'http://www.behindkink.com/2014/08/14/ab1576-performers-voice-finally-heard-the-bill-is-killed/',
'md5': '41ad01222b8442089a55528fec43ec01',
'info_dict': {
'id': '36370',
'ext': 'mp4',
'title': 'AB1576 - PERFORMERS VOICE FINALLY HEARD - THE BILL IS KILLED!',
'description': 'The adult industry voice was finally heard as Assembly Bill 1576 remained\xa0 in suspense today at the Senate Appropriations Hearing. AB1576 was, among other industry damaging issues, a condom mandate...',
'upload_date': '20140814',
'thumbnail': 'http://www.behindkink.com/wp-content/uploads/2014/08/36370_AB1576_Win.jpg',
'age_limit': 18,
}
}
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
display_id = mobj.group('id')
year = mobj.group('year')
month = mobj.group('month')
day = mobj.group('day')
upload_date = year + month + day
webpage = self._download_webpage(url, display_id)
video_url = self._search_regex(
r"'file':\s*'([^']+)'",
webpage, 'URL base')
video_id = url_basename(video_url)
video_id = video_id.split('_')[0]
return {
'id': video_id,
'url': video_url,
'ext': 'mp4',
'title': self._og_search_title(webpage),
'display_id': display_id,
'thumbnail': self._og_search_thumbnail(webpage),
'description': self._og_search_description(webpage),
'upload_date': upload_date,
'age_limit': 18,
}

View File

@ -0,0 +1,39 @@
#coding: utf-8
from __future__ import unicode_literals
from .common import InfoExtractor
from ..utils import int_or_none
class BildIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?bild\.de/(?:[^/]+/)+(?P<display_id>[^/]+)-(?P<id>\d+)(?:,auto=true)?\.bild\.html'
IE_DESC = 'Bild.de'
_TEST = {
'url': 'http://www.bild.de/video/clip/apple-ipad-air/das-koennen-die-neuen-ipads-38184146.bild.html',
'md5': 'dd495cbd99f2413502a1713a1156ac8a',
'info_dict': {
'id': '38184146',
'ext': 'mp4',
'title': 'BILD hat sie getestet',
'thumbnail': 'http://bilder.bild.de/fotos/stand-das-koennen-die-neuen-ipads-38184138/Bild/1.bild.jpg',
'duration': 196,
'description': 'Mit dem iPad Air 2 und dem iPad Mini 3 hat Apple zwei neue Tablet-Modelle präsentiert. BILD-Reporter Sven Stein durfte die Geräte bereits testen. ',
}
}
def _real_extract(self, url):
video_id = self._match_id(url)
xml_url = url.split(".bild.html")[0] + ",view=xml.bild.xml"
doc = self._download_xml(xml_url, video_id)
duration = int_or_none(doc.attrib.get('duration'), scale=1000)
return {
'id': video_id,
'title': doc.attrib['ueberschrift'],
'description': doc.attrib.get('text'),
'url': doc.attrib['src'],
'thumbnail': doc.attrib.get('img'),
'duration': duration,
}

View File

@ -1,8 +1,6 @@
# coding: utf-8 # coding: utf-8
from __future__ import unicode_literals from __future__ import unicode_literals
import re
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import ( from ..utils import (
ExtractorError, ExtractorError,
@ -26,6 +24,8 @@ class BRIE(InfoExtractor):
'title': 'Wenn das Traditions-Theater wackelt', 'title': 'Wenn das Traditions-Theater wackelt',
'description': 'Heimatsound-Festival 2014: Wenn das Traditions-Theater wackelt', 'description': 'Heimatsound-Festival 2014: Wenn das Traditions-Theater wackelt',
'duration': 34, 'duration': 34,
'uploader': 'BR',
'upload_date': '20140802',
} }
}, },
{ {
@ -66,8 +66,7 @@ class BRIE(InfoExtractor):
] ]
def _real_extract(self, url): def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url) display_id = self._match_id(url)
display_id = mobj.group('id')
page = self._download_webpage(url, display_id) page = self._download_webpage(url, display_id)
xml_url = self._search_regex( xml_url = self._search_regex(
r"return BRavFramework\.register\(BRavFramework\('avPlayer_(?:[a-f0-9-]{36})'\)\.setup\({dataURL:'(/(?:[a-z0-9\-]+/)+[a-z0-9/~_.-]+)'}\)\);", page, 'XMLURL') r"return BRavFramework\.register\(BRavFramework\('avPlayer_(?:[a-f0-9-]{36})'\)\.setup\({dataURL:'(/(?:[a-z0-9\-]+/)+[a-z0-9/~_.-]+)'}\)\);", page, 'XMLURL')

View File

@ -4,37 +4,61 @@ import re
import json import json
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import (
int_or_none,
parse_age_limit,
)
class BreakIE(InfoExtractor): class BreakIE(InfoExtractor):
_VALID_URL = r'http://(?:www\.)?break\.com/video/([^/]+)' _VALID_URL = r'http://(?:www\.)?break\.com/video/(?:[^/]+/)*.+-(?P<id>\d+)'
_TEST = { _TESTS = [{
'url': 'http://www.break.com/video/when-girls-act-like-guys-2468056', 'url': 'http://www.break.com/video/when-girls-act-like-guys-2468056',
'md5': 'a3513fb1547fba4fb6cfac1bffc6c46b', 'md5': '33aa4ff477ecd124d18d7b5d23b87ce5',
'info_dict': { 'info_dict': {
'id': '2468056', 'id': '2468056',
'ext': 'mp4', 'ext': 'mp4',
'title': 'When Girls Act Like D-Bags', 'title': 'When Girls Act Like D-Bags',
} }
} }, {
'url': 'http://www.break.com/video/ugc/baby-flex-2773063',
'only_matching': True,
}]
def _real_extract(self, url): def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url) video_id = self._match_id(url)
video_id = mobj.group(1).split("-")[-1] webpage = self._download_webpage(
embed_url = 'http://www.break.com/embed/%s' % video_id 'http://www.break.com/embed/%s' % video_id, video_id)
webpage = self._download_webpage(embed_url, video_id) info = json.loads(self._search_regex(
info_json = self._search_regex(r'var embedVars = ({.*})\s*?</script>', r'var embedVars = ({.*})\s*?</script>',
webpage, 'info json', flags=re.DOTALL) webpage, 'info json', flags=re.DOTALL))
info = json.loads(info_json)
video_url = info['videoUri']
youtube_id = info.get('youtubeId') youtube_id = info.get('youtubeId')
if youtube_id: if youtube_id:
return self.url_result(youtube_id, 'Youtube') return self.url_result(youtube_id, 'Youtube')
final_url = video_url + '?' + info['AuthToken'] formats = [{
'url': media['uri'] + '?' + info['AuthToken'],
'tbr': media['bitRate'],
'width': media['width'],
'height': media['height'],
} for media in info['media']]
if not formats:
formats.append({
'url': info['videoUri']
})
self._sort_formats(formats)
duration = int_or_none(info.get('videoLengthInSeconds'))
age_limit = parse_age_limit(info.get('audienceRating'))
return { return {
'id': video_id, 'id': video_id,
'url': final_url,
'title': info['contentName'], 'title': info['contentName'],
'thumbnail': info['thumbUri'], 'thumbnail': info['thumbUri'],
'duration': duration,
'age_limit': age_limit,
'formats': formats,
} }

View File

@ -87,6 +87,15 @@ class BrightcoveIE(InfoExtractor):
'description': 'UCI MTB World Cup 2014: Fort William, UK - Downhill Finals', 'description': 'UCI MTB World Cup 2014: Fort William, UK - Downhill Finals',
}, },
}, },
{
# playlist test
# from http://support.brightcove.com/en/video-cloud/docs/playlist-support-single-video-players
'url': 'http://c.brightcove.com/services/viewer/htmlFederated?playerID=3550052898001&playerKey=AQ%7E%7E%2CAAABmA9XpXk%7E%2C-Kp7jNgisre1fG5OdqpAFUTcs0lP_ZoL',
'info_dict': {
'title': 'Sealife',
},
'playlist_mincount': 7,
},
] ]
@classmethod @classmethod

View File

@ -7,15 +7,21 @@ from .common import InfoExtractor
from ..utils import ( from ..utils import (
unified_strdate, unified_strdate,
url_basename, url_basename,
qualities,
) )
class CanalplusIE(InfoExtractor): class CanalplusIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.canalplus\.fr/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>[0-9]+))' IE_DESC = 'canalplus.fr, piwiplus.fr and d8.tv'
_VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/cplus/%s' _VALID_URL = r'https?://(?:www\.(?P<site>canalplus\.fr|piwiplus\.fr|d8\.tv)/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>[0-9]+))'
IE_NAME = 'canalplus.fr' _VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/%s/%s'
_SITE_ID_MAP = {
'canalplus.fr': 'cplus',
'piwiplus.fr': 'teletoon',
'd8.tv': 'd8',
}
_TEST = { _TESTS = [{
'url': 'http://www.canalplus.fr/c-infos-documentaires/pid1830-c-zapping.html?vid=922470', 'url': 'http://www.canalplus.fr/c-infos-documentaires/pid1830-c-zapping.html?vid=922470',
'md5': '3db39fb48b9685438ecf33a1078023e4', 'md5': '3db39fb48b9685438ecf33a1078023e4',
'info_dict': { 'info_dict': {
@ -25,36 +31,73 @@ class CanalplusIE(InfoExtractor):
'description': 'Le meilleur de toutes les chaînes, tous les jours.\nEmission du 26 août 2013', 'description': 'Le meilleur de toutes les chaînes, tous les jours.\nEmission du 26 août 2013',
'upload_date': '20130826', 'upload_date': '20130826',
}, },
} }, {
'url': 'http://www.piwiplus.fr/videos-piwi/pid1405-le-labyrinthe-boing-super-ranger.html?vid=1108190',
'info_dict': {
'id': '1108190',
'ext': 'flv',
'title': 'Le labyrinthe - Boing super ranger',
'description': 'md5:4cea7a37153be42c1ba2c1d3064376ff',
'upload_date': '20140724',
},
'skip': 'Only works from France',
}, {
'url': 'http://www.d8.tv/d8-docs-mags/pid6589-d8-campagne-intime.html',
'info_dict': {
'id': '966289',
'ext': 'flv',
'title': 'Campagne intime - Documentaire exceptionnel',
'description': 'md5:d2643b799fb190846ae09c61e59a859f',
'upload_date': '20131108',
},
'skip': 'videos get deleted after a while',
}]
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.groupdict().get('id') video_id = mobj.groupdict().get('id')
site_id = self._SITE_ID_MAP[mobj.group('site') or 'canal']
# Beware, some subclasses do not define an id group # Beware, some subclasses do not define an id group
display_id = url_basename(mobj.group('path')) display_id = url_basename(mobj.group('path'))
if video_id is None: if video_id is None:
webpage = self._download_webpage(url, display_id) webpage = self._download_webpage(url, display_id)
video_id = self._search_regex(r'<canal:player videoId="(\d+)"', webpage, 'video id') video_id = self._search_regex(
r'<canal:player[^>]+?videoId="(\d+)"', webpage, 'video id')
info_url = self._VIDEO_INFO_TEMPLATE % video_id info_url = self._VIDEO_INFO_TEMPLATE % (site_id, video_id)
doc = self._download_xml(info_url, video_id, 'Downloading video XML') doc = self._download_xml(info_url, video_id, 'Downloading video XML')
video_info = [video for video in doc if video.find('ID').text == video_id][0] video_info = [video for video in doc if video.find('ID').text == video_id][0]
media = video_info.find('MEDIA') media = video_info.find('MEDIA')
infos = video_info.find('INFOS') infos = video_info.find('INFOS')
preferences = ['MOBILE', 'BAS_DEBIT', 'HAUT_DEBIT', 'HD', 'HLS', 'HDS'] preference = qualities(['MOBILE', 'BAS_DEBIT', 'HAUT_DEBIT', 'HD', 'HLS', 'HDS'])
formats = [ formats = []
{ for fmt in media.find('VIDEOS'):
'url': fmt.text + '?hdcore=2.11.3' if fmt.tag == 'HDS' else fmt.text, format_url = fmt.text
'format_id': fmt.tag, if not format_url:
'ext': 'mp4' if fmt.tag == 'HLS' else 'flv', continue
'preference': preferences.index(fmt.tag) if fmt.tag in preferences else -1, format_id = fmt.tag
} for fmt in media.find('VIDEOS') if fmt.text if format_id == 'HLS':
] hls_formats = self._extract_m3u8_formats(format_url, video_id, 'flv')
for fmt in hls_formats:
fmt['preference'] = preference(format_id)
formats.extend(hls_formats)
elif format_id == 'HDS':
hds_formats = self._extract_f4m_formats(format_url + '?hdcore=2.11.3', video_id)
for fmt in hds_formats:
fmt['preference'] = preference(format_id)
formats.extend(hds_formats)
else:
formats.append({
'url': format_url,
'format_id': format_id,
'preference': preference(format_id),
})
self._sort_formats(formats) self._sort_formats(formats)
return { return {

View File

@ -42,7 +42,7 @@ class CinemassacreIE(InfoExtractor):
webpage = self._download_webpage(url, display_id) webpage = self._download_webpage(url, display_id)
video_date = mobj.group('date_Y') + mobj.group('date_m') + mobj.group('date_d') video_date = mobj.group('date_Y') + mobj.group('date_m') + mobj.group('date_d')
mobj = re.search(r'src="(?P<embed_url>http://player\.screenwavemedia\.com/play/[a-zA-Z]+\.php\?id=(?:Cinemassacre-)?(?P<video_id>.+?))"', webpage) mobj = re.search(r'src="(?P<embed_url>http://player\.screenwavemedia\.com/play/[a-zA-Z]+\.php\?[^"]*\bid=(?:Cinemassacre-)?(?P<video_id>.+?))"', webpage)
if not mobj: if not mobj:
raise ExtractorError('Can\'t extract embed url and video id') raise ExtractorError('Can\'t extract embed url and video id')
playerdata_url = mobj.group('embed_url') playerdata_url = mobj.group('embed_url')
@ -53,17 +53,19 @@ class CinemassacreIE(InfoExtractor):
video_description = self._html_search_regex( video_description = self._html_search_regex(
r'<div class="entry-content">(?P<description>.+?)</div>', r'<div class="entry-content">(?P<description>.+?)</div>',
webpage, 'description', flags=re.DOTALL, fatal=False) webpage, 'description', flags=re.DOTALL, fatal=False)
video_thumbnail = self._og_search_thumbnail(webpage)
playerdata = self._download_webpage(playerdata_url, video_id, 'Downloading player webpage') playerdata = self._download_webpage(playerdata_url, video_id, 'Downloading player webpage')
video_thumbnail = self._search_regex(
r'image: \'(?P<thumbnail>[^\']+)\'', playerdata, 'thumbnail', fatal=False)
sd_url = self._search_regex(r'file: \'([^\']+)\', label: \'SD\'', playerdata, 'sd_file')
videolist_url = self._search_regex(r'file: \'([^\']+\.smil)\'}', playerdata, 'videolist_url')
vidurl = self._search_regex(
r'\'vidurl\'\s*:\s*"([^\']+)"', playerdata, 'vidurl').replace('\\/', '/')
videolist_url = self._search_regex(
r"file\s*:\s*'(http.+?/jwplayer\.smil)'", playerdata, 'jwplayer.smil')
videolist = self._download_xml(videolist_url, video_id, 'Downloading videolist XML') videolist = self._download_xml(videolist_url, video_id, 'Downloading videolist XML')
formats = [] formats = []
baseurl = sd_url[:sd_url.rfind('/')+1] baseurl = vidurl[:vidurl.rfind('/')+1]
for video in videolist.findall('.//video'): for video in videolist.findall('.//video'):
src = video.get('src') src = video.get('src')
if not src: if not src:

View File

@ -4,7 +4,6 @@ import json
import re import re
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import int_or_none
_translation_table = { _translation_table = {
@ -35,14 +34,11 @@ class CliphunterIE(InfoExtractor):
'title': 'Fun Jynx Maze solo', 'title': 'Fun Jynx Maze solo',
'thumbnail': 're:^https?://.*\.jpg$', 'thumbnail': 're:^https?://.*\.jpg$',
'age_limit': 18, 'age_limit': 18,
'duration': 1317,
} }
} }
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_title = self._search_regex( video_title = self._search_regex(
@ -86,14 +82,11 @@ class CliphunterIE(InfoExtractor):
thumbnail = self._search_regex( thumbnail = self._search_regex(
r"var\s+mov_thumb\s*=\s*'([^']+)';", r"var\s+mov_thumb\s*=\s*'([^']+)';",
webpage, 'thumbnail', fatal=False) webpage, 'thumbnail', fatal=False)
duration = int_or_none(self._search_regex(
r'pl_dur\s*=\s*([0-9]+)', webpage, 'duration', fatal=False))
return { return {
'id': video_id, 'id': video_id,
'title': video_title, 'title': video_title,
'formats': formats, 'formats': formats,
'duration': duration,
'age_limit': self._rta_search(webpage), 'age_limit': self._rta_search(webpage),
'thumbnail': thumbnail, 'thumbnail': thumbnail,
} }

View File

@ -9,6 +9,8 @@ from ..utils import (
compat_parse_qs, compat_parse_qs,
compat_urllib_parse, compat_urllib_parse,
remove_end, remove_end,
HEADRequest,
compat_HTTPError,
) )
@ -21,6 +23,7 @@ class CloudyIE(InfoExtractor):
''' '''
_EMBED_URL = 'http://www.%s/embed.php?id=%s' _EMBED_URL = 'http://www.%s/embed.php?id=%s'
_API_URL = 'http://www.%s/api/player.api.php?%s' _API_URL = 'http://www.%s/api/player.api.php?%s'
_MAX_TRIES = 2
_TESTS = [ _TESTS = [
{ {
'url': 'https://www.cloudy.ec/v/af511e2527aac', 'url': 'https://www.cloudy.ec/v/af511e2527aac',
@ -42,24 +45,30 @@ class CloudyIE(InfoExtractor):
} }
] ]
def _real_extract(self, url): def _extract_video(self, video_host, video_id, file_key, error_url=None, try_num=0):
mobj = re.match(self._VALID_URL, url)
video_host = mobj.group('host')
video_id = mobj.group('id')
url = self._EMBED_URL % (video_host, video_id) if try_num > self._MAX_TRIES - 1:
webpage = self._download_webpage(url, video_id) raise ExtractorError('Unable to extract video URL', expected=True)
file_key = self._search_regex( form = {
r'filekey\s*=\s*"([^"]+)"', webpage, 'file_key')
data_url = self._API_URL % (video_host, compat_urllib_parse.urlencode({
'file': video_id, 'file': video_id,
'key': file_key, 'key': file_key,
})) }
if error_url:
form.update({
'numOfErrors': try_num,
'errorCode': '404',
'errorUrl': error_url,
})
data_url = self._API_URL % (video_host, compat_urllib_parse.urlencode(form))
player_data = self._download_webpage( player_data = self._download_webpage(
data_url, video_id, 'Downloading player data') data_url, video_id, 'Downloading player data')
data = compat_parse_qs(player_data) data = compat_parse_qs(player_data)
try_num += 1
if 'error' in data: if 'error' in data:
raise ExtractorError( raise ExtractorError(
'%s error: %s' % (self.IE_NAME, ' '.join(data['error_msg'])), '%s error: %s' % (self.IE_NAME, ' '.join(data['error_msg'])),
@ -69,16 +78,31 @@ class CloudyIE(InfoExtractor):
if title: if title:
title = remove_end(title, '&asdasdas').strip() title = remove_end(title, '&asdasdas').strip()
formats = []
video_url = data.get('url', [None])[0] video_url = data.get('url', [None])[0]
if video_url: if video_url:
formats.append({ try:
'format_id': 'sd', self._request_webpage(HEADRequest(video_url), video_id, 'Checking video URL')
'url': video_url, except ExtractorError as e:
}) if isinstance(e.cause, compat_HTTPError) and e.cause.code in [404, 410]:
self.report_warning('Invalid video URL, requesting another', video_id)
return self._extract_video(video_host, video_id, file_key, video_url, try_num)
return { return {
'id': video_id, 'id': video_id,
'url': video_url,
'title': title, 'title': title,
'formats': formats,
} }
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
video_host = mobj.group('host')
video_id = mobj.group('id')
url = self._EMBED_URL % (video_host, video_id)
webpage = self._download_webpage(url, video_id)
file_key = self._search_regex(
r'filekey\s*=\s*"([^"]+)"', webpage, 'file_key')
return self._extract_video(video_host, video_id, file_key)

View File

@ -12,7 +12,7 @@ from ..utils import (
class CNNIE(InfoExtractor): class CNNIE(InfoExtractor):
_VALID_URL = r'''(?x)https?://((edition|www)\.)?cnn\.com/video/(data/.+?|\?)/ _VALID_URL = r'''(?x)https?://((edition|www)\.)?cnn\.com/video/(data/.+?|\?)/
(?P<path>.+?/(?P<title>[^/]+?)(?:\.cnn|(?=&)))''' (?P<path>.+?/(?P<title>[^/]+?)(?:\.cnn(-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',

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import base64 import base64
import datetime
import hashlib import hashlib
import json import json
import netrc import netrc
@ -15,11 +16,13 @@ from ..utils import (
compat_http_client, compat_http_client,
compat_urllib_error, compat_urllib_error,
compat_urllib_parse_urlparse, compat_urllib_parse_urlparse,
compat_urlparse,
compat_str, compat_str,
clean_html, clean_html,
compiled_regex_type, compiled_regex_type,
ExtractorError, ExtractorError,
float_or_none,
int_or_none, int_or_none,
RegexNotFoundError, RegexNotFoundError,
sanitize_filename, sanitize_filename,
@ -69,6 +72,7 @@ class InfoExtractor(object):
* acodec Name of the audio codec in use * acodec Name of the audio codec in use
* asr Audio sampling rate in Hertz * asr Audio sampling rate in Hertz
* vbr Average video bitrate in KBit/s * vbr Average video bitrate in KBit/s
* fps Frame rate
* vcodec Name of the video codec in use * vcodec Name of the video codec in use
* container Name of the container format * container Name of the container format
* filesize The number of bytes, if known in advance * filesize The number of bytes, if known in advance
@ -86,6 +90,10 @@ class InfoExtractor(object):
format, irrespective of the file format. format, irrespective of the file format.
-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.
* source_preference Order number for this video source
(quality takes higher priority)
-1 for default (order by other properties),
-2 or smaller for less than default.
* http_referer HTTP Referer header value to set. * 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
@ -130,9 +138,13 @@ class InfoExtractor(object):
by YoutubeDL if it's missing) by YoutubeDL if it's missing)
categories: A list of categories that the video falls in, for example categories: A list of categories that the video falls in, for example
["Sports", "Berlin"] ["Sports", "Berlin"]
is_live: True, False, or None (=unknown). Whether this video is a
live stream that goes on instead of a fixed-length video.
Unless mentioned otherwise, the fields should be Unicode strings. Unless mentioned otherwise, the fields should be Unicode strings.
Unless mentioned otherwise, None is equivalent to absence of information.
Subclasses of this one should re-define the _real_initialize() and Subclasses of this one should re-define the _real_initialize() and
_real_extract() methods and define a _VALID_URL regexp. _real_extract() methods and define a _VALID_URL regexp.
Probably, they should also be added to the list of extractors. Probably, they should also be added to the list of extractors.
@ -161,6 +173,14 @@ class InfoExtractor(object):
cls._VALID_URL_RE = re.compile(cls._VALID_URL) cls._VALID_URL_RE = re.compile(cls._VALID_URL)
return cls._VALID_URL_RE.match(url) is not None return cls._VALID_URL_RE.match(url) is not None
@classmethod
def _match_id(cls, url):
if '_VALID_URL_RE' not in cls.__dict__:
cls._VALID_URL_RE = re.compile(cls._VALID_URL)
m = cls._VALID_URL_RE.match(url)
assert m
return m.group('id')
@classmethod @classmethod
def working(cls): def working(cls):
"""Getter method for _WORKING.""" """Getter method for _WORKING."""
@ -223,7 +243,6 @@ class InfoExtractor(object):
def _download_webpage_handle(self, url_or_request, video_id, note=None, errnote=None, fatal=True): def _download_webpage_handle(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
""" Returns a tuple (page content as string, URL handle) """ """ Returns a tuple (page content as string, URL handle) """
# Strip hashes from the URL (#1038) # Strip hashes from the URL (#1038)
if isinstance(url_or_request, (compat_str, str)): if isinstance(url_or_request, (compat_str, str)):
url_or_request = url_or_request.partition('#')[0] url_or_request = url_or_request.partition('#')[0]
@ -232,6 +251,10 @@ class InfoExtractor(object):
if urlh is False: if urlh is False:
assert not fatal assert not fatal
return False return False
content = self._webpage_read_content(urlh, url_or_request, video_id, note, errnote, fatal)
return (content, urlh)
def _webpage_read_content(self, urlh, url_or_request, video_id, note=None, errnote=None, fatal=True):
content_type = urlh.headers.get('Content-Type', '') content_type = urlh.headers.get('Content-Type', '')
webpage_bytes = urlh.read() webpage_bytes = urlh.read()
m = re.match(r'[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+\s*;\s*charset=(.+)', content_type) m = re.match(r'[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+\s*;\s*charset=(.+)', content_type)
@ -266,6 +289,12 @@ class InfoExtractor(object):
raw_filename = basen + '.dump' raw_filename = basen + '.dump'
filename = sanitize_filename(raw_filename, restricted=True) filename = sanitize_filename(raw_filename, restricted=True)
self.to_screen('Saving request to ' + filename) self.to_screen('Saving request to ' + filename)
# Working around MAX_PATH limitation on Windows (see
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
if os.name == 'nt':
absfilepath = os.path.abspath(filename)
if len(absfilepath) > 259:
filename = '\\\\?\\' + absfilepath
with open(filename, 'wb') as outf: with open(filename, 'wb') as outf:
outf.write(webpage_bytes) outf.write(webpage_bytes)
@ -284,7 +313,7 @@ class InfoExtractor(object):
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)
return (content, urlh) 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):
""" Returns the data of the page as a string """ """ Returns the data of the page as a string """
@ -321,7 +350,11 @@ class InfoExtractor(object):
try: try:
return json.loads(json_string) return json.loads(json_string)
except ValueError as ve: except ValueError as ve:
raise ExtractorError('Failed to download JSON', cause=ve) errmsg = '%s: Failed to parse JSON ' % video_id
if fatal:
raise ExtractorError(errmsg, cause=ve)
else:
self.report_warning(errmsg + str(ve))
def report_warning(self, msg, video_id=None): def report_warning(self, msg, video_id=None):
idstr = '' if video_id is None else '%s: ' % video_id idstr = '' if video_id is None else '%s: ' % video_id
@ -586,14 +619,16 @@ class InfoExtractor(object):
f.get('vbr') if f.get('vbr') 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('filesize') if f.get('filesize') 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('format_id'), f.get('format_id'),
) )
formats.sort(key=_formats_key) formats.sort(key=_formats_key)
def http_scheme(self): def http_scheme(self):
""" Either "https:" or "https:", depending on the user's preferences """ """ Either "http:" or "https:", depending on the user's preferences """
return ( return (
'http:' 'http:'
if self._downloader.params.get('prefer_insecure', False) if self._downloader.params.get('prefer_insecure', False)
@ -638,7 +673,9 @@ class InfoExtractor(object):
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):
formats = [{ formats = [{
'format_id': 'm3u8-meta', 'format_id': 'm3u8-meta',
'url': m3u8_url, 'url': m3u8_url,
@ -649,7 +686,15 @@ class InfoExtractor(object):
'format_note': 'Quality selection URL', 'format_note': 'Quality selection URL',
}] }]
m3u8_doc = self._download_webpage(m3u8_url, video_id) format_url = lambda u: (
u
if re.match(r'^https?://', u)
else compat_urlparse.urljoin(m3u8_url, u))
m3u8_doc = self._download_webpage(
m3u8_url, video_id,
note='Downloading m3u8 information',
errnote='Failed to download m3u8 information')
last_info = None last_info = 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>"[^"]+"|[^",]+)(?:,|$)')
@ -665,15 +710,17 @@ class InfoExtractor(object):
continue continue
else: else:
if last_info is None: if last_info is None:
formats.append({'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': 'm3u8-%d' % (tbr if tbr else len(formats)),
'url': line.strip(), 'url': format_url(line.strip()),
'tbr': tbr, 'tbr': tbr,
'ext': ext, 'ext': ext,
'protocol': entry_protocol,
'preference': preference,
} }
codecs = last_info.get('CODECS') codecs = last_info.get('CODECS')
if codecs: if codecs:
@ -693,6 +740,34 @@ class InfoExtractor(object):
self._sort_formats(formats) self._sort_formats(formats)
return formats return formats
def _live_title(self, name):
""" Generate the title for a live video """
now = datetime.datetime.now()
now_str = now.strftime("%Y-%m-%d %H:%M")
return name + ' ' + now_str
def _int(self, v, name, fatal=False, **kwargs):
res = int_or_none(v, **kwargs)
if 'get_attr' in kwargs:
print(getattr(v, kwargs['get_attr']))
if res is None:
msg = 'Failed to extract %s: Could not parse value %r' % (name, v)
if fatal:
raise ExtractorError(msg)
else:
self._downloader.report_warning(msg)
return res
def _float(self, v, name, fatal=False, **kwargs):
res = float_or_none(v, **kwargs)
if res is None:
msg = 'Failed to extract %s: Could not parse value %r' % (name, v)
if fatal:
raise ExtractorError(msg)
else:
self._downloader.report_warning(msg)
return res
class SearchInfoExtractor(InfoExtractor): class SearchInfoExtractor(InfoExtractor):
""" """

View File

@ -34,6 +34,8 @@ class CondeNastIE(InfoExtractor):
_VALID_URL = r'http://(video|www|player)\.(?P<site>%s)\.com/(?P<type>watch|series|video|embed)/(?P<id>[^/?#]+)' % '|'.join(_SITES.keys()) _VALID_URL = r'http://(video|www|player)\.(?P<site>%s)\.com/(?P<type>watch|series|video|embed)/(?P<id>[^/?#]+)' % '|'.join(_SITES.keys())
IE_DESC = 'Condé Nast media group: %s' % ', '.join(sorted(_SITES.values())) IE_DESC = 'Condé Nast media group: %s' % ', '.join(sorted(_SITES.values()))
EMBED_URL = r'(?:https?:)?//player\.(?P<site>%s)\.com/(?P<type>embed)/.+?' % '|'.join(_SITES.keys())
_TEST = { _TEST = {
'url': 'http://video.wired.com/watch/3d-printed-speakers-lit-with-led', 'url': 'http://video.wired.com/watch/3d-printed-speakers-lit-with-led',
'md5': '1921f713ed48aabd715691f774c451f7', 'md5': '1921f713ed48aabd715691f774c451f7',

View File

@ -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 .common import InfoExtractor from .subtitles import SubtitlesInfoExtractor
from ..utils import ( from ..utils import (
ExtractorError, ExtractorError,
compat_urllib_parse, compat_urllib_parse,
@ -24,9 +24,10 @@ from ..aes import (
aes_cbc_decrypt, aes_cbc_decrypt,
inc, inc,
) )
from .common import InfoExtractor
class CrunchyrollIE(InfoExtractor): class CrunchyrollIE(SubtitlesInfoExtractor):
_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/(?:[^/]*/[^/?&]*?|media/\?id=)(?P<video_id>[0-9]+))(?:[/?&]|$)'
_TEST = { _TEST = {
'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',
@ -39,6 +40,7 @@ class CrunchyrollIE(InfoExtractor):
'thumbnail': 'http://img1.ak.crunchyroll.com/i/spire1-tmb/20c6b5e10f1a47b10516877d3c039cae1380951166_full.jpg', 'thumbnail': 'http://img1.ak.crunchyroll.com/i/spire1-tmb/20c6b5e10f1a47b10516877d3c039cae1380951166_full.jpg',
'uploader': 'Yomiuri Telecasting Corporation (YTV)', 'uploader': 'Yomiuri Telecasting Corporation (YTV)',
'upload_date': '20131013', 'upload_date': '20131013',
'url': 're:(?!.*&amp)',
}, },
'params': { 'params': {
# rtmp # rtmp
@ -107,19 +109,17 @@ class CrunchyrollIE(InfoExtractor):
decrypted_data = intlist_to_bytes(aes_cbc_decrypt(data, key, iv)) decrypted_data = intlist_to_bytes(aes_cbc_decrypt(data, key, iv))
return zlib.decompress(decrypted_data) return zlib.decompress(decrypted_data)
def _convert_subtitles_to_srt(self, subtitles): def _convert_subtitles_to_srt(self, sub_root):
output = '' output = ''
for i, (start, end, text) in enumerate(re.findall(r'<event [^>]*?start="([^"]+)" [^>]*?end="([^"]+)" [^>]*?text="([^"]+)"[^>]*?>', subtitles), 1):
start = start.replace('.', ',') for i, event in enumerate(sub_root.findall('./events/event'), 1):
end = end.replace('.', ',') start = event.attrib['start'].replace('.', ',')
text = clean_html(text) end = event.attrib['end'].replace('.', ',')
text = text.replace('\\N', '\n') text = event.attrib['text'].replace('\\N', '\n')
if not text:
continue
output += '%d\n%s --> %s\n%s\n\n' % (i, start, end, text) output += '%d\n%s --> %s\n%s\n\n' % (i, start, end, text)
return output return output
def _convert_subtitles_to_ass(self, subtitles): def _convert_subtitles_to_ass(self, sub_root):
output = '' output = ''
def ass_bool(strvalue): def ass_bool(strvalue):
@ -128,10 +128,6 @@ class CrunchyrollIE(InfoExtractor):
assvalue = '-1' assvalue = '-1'
return assvalue return assvalue
sub_root = xml.etree.ElementTree.fromstring(subtitles)
if not sub_root:
return output
output = '[Script Info]\n' output = '[Script Info]\n'
output += 'Title: %s\n' % sub_root.attrib["title"] output += 'Title: %s\n' % sub_root.attrib["title"]
output += 'ScriptType: v4.00+\n' output += 'ScriptType: v4.00+\n'
@ -237,12 +233,14 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
streamdata_req.data = 'req=RpcApiVideoEncode%5FGetStreamInfo&video%5Fencode%5Fquality='+stream_quality+'&media%5Fid='+stream_id+'&video%5Fformat='+stream_format streamdata_req.data = 'req=RpcApiVideoEncode%5FGetStreamInfo&video%5Fencode%5Fquality='+stream_quality+'&media%5Fid='+stream_id+'&video%5Fformat='+stream_format
streamdata_req.add_header('Content-Type', 'application/x-www-form-urlencoded') streamdata_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
streamdata_req.add_header('Content-Length', str(len(streamdata_req.data))) streamdata_req.add_header('Content-Length', str(len(streamdata_req.data)))
streamdata = self._download_webpage(streamdata_req, video_id, note='Downloading media info for '+video_format) streamdata = self._download_xml(
video_url = self._search_regex(r'<host>([^<]+)', streamdata, 'video_url') streamdata_req, video_id,
video_play_path = self._search_regex(r'<file>([^<]+)', streamdata, 'video_play_path') note='Downloading media info for %s' % video_format)
video_url = streamdata.find('.//host').text
video_play_path = streamdata.find('.//file').text
formats.append({ formats.append({
'url': video_url, 'url': video_url,
'play_path': video_play_path, 'play_path': video_play_path,
'ext': 'flv', 'ext': 'flv',
'format': video_format, 'format': video_format,
'format_id': video_format, 'format_id': video_format,
@ -266,10 +264,17 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False) lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False)
if not lang_code: if not lang_code:
continue continue
sub_root = xml.etree.ElementTree.fromstring(subtitle)
if not sub_root:
subtitles[lang_code] = ''
if sub_format == 'ass': if sub_format == 'ass':
subtitles[lang_code] = self._convert_subtitles_to_ass(subtitle) subtitles[lang_code] = self._convert_subtitles_to_ass(sub_root)
else: else:
subtitles[lang_code] = self._convert_subtitles_to_srt(subtitle) 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,
@ -281,3 +286,40 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
'subtitles': subtitles, 'subtitles': subtitles,
'formats': formats, 'formats': formats,
} }
class CrunchyrollShowPlaylistIE(InfoExtractor):
IE_NAME = "crunchyroll:playlist"
_VALID_URL = r'https?://(?:(?P<prefix>www|m)\.)?(?P<url>crunchyroll\.com/(?!(?:news|anime-news|library|forum|launchcalendar|lineup|store|comics|freetrial|login))(?P<id>[\w\-]+))/?$'
_TESTS = [{
'url': 'http://www.crunchyroll.com/a-bridge-to-the-starry-skies-hoshizora-e-kakaru-hashi',
'info_dict': {
'id': 'a-bridge-to-the-starry-skies-hoshizora-e-kakaru-hashi',
'title': 'A Bridge to the Starry Skies - Hoshizora e Kakaru Hashi'
},
'playlist_count': 13,
}]
def _real_extract(self, url):
show_id = self._match_id(url)
webpage = self._download_webpage(url, show_id)
title = self._html_search_regex(
r'(?s)<h1[^>]*>\s*<span itemprop="name">(.*?)</span>',
webpage, 'title')
episode_paths = re.findall(
r'(?s)<li id="showview_videos_media_[0-9]+"[^>]+>.*?<a href="([^"]+)"',
webpage)
entries = [
self.url_result('http://www.crunchyroll.com' + ep, 'Crunchyroll')
for ep in episode_paths
]
entries.reverse()
return {
'_type': 'playlist',
'id': show_id,
'title': title,
'entries': entries,
}

View File

@ -1,25 +0,0 @@
# encoding: utf-8
from __future__ import unicode_literals
from .canalplus import CanalplusIE
class D8IE(CanalplusIE):
_VALID_URL = r'https?://www\.d8\.tv/.*?/(?P<path>.*)'
_VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/d8/%s'
IE_NAME = 'd8.tv'
_TEST = {
'url': 'http://www.d8.tv/d8-docs-mags/pid6589-d8-campagne-intime.html',
'file': '966289.flv',
'info_dict': {
'title': 'Campagne intime - Documentaire exceptionnel',
'description': 'md5:d2643b799fb190846ae09c61e59a859f',
'upload_date': '20131108',
},
'params': {
# rtmp
'skip_download': True,
},
'skip': 'videos get deleted after a while',
}

View File

@ -82,11 +82,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):
] ]
def _real_extract(self, url): def _real_extract(self, url):
# Extract id and simplified title from URL video_id = self._match_id(url)
mobj = re.match(self._VALID_URL, url)
video_id = mobj.group('id')
url = 'http://www.dailymotion.com/video/%s' % video_id url = 'http://www.dailymotion.com/video/%s' % video_id
# Retrieve video webpage to extract further information # Retrieve video webpage to extract further information
@ -147,18 +143,23 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):
self._list_available_subtitles(video_id, webpage) self._list_available_subtitles(video_id, webpage)
return return
view_count = self._search_regex( view_count = str_to_int(self._search_regex(
r'video_views_count[^>]+>\s+([\d\.,]+)', webpage, 'view count', fatal=False) r'video_views_count[^>]+>\s+([\d\.,]+)',
if view_count is not None: webpage, 'view count', fatal=False))
view_count = str_to_int(view_count)
title = self._og_search_title(webpage, default=None)
if title is None:
title = self._html_search_regex(
r'(?s)<span\s+id="video_title"[^>]*>(.*?)</span>', webpage,
'title')
return { return {
'id': video_id, 'id': video_id,
'formats': formats, 'formats': formats,
'uploader': info['owner.screenname'], 'uploader': info['owner.screenname'],
'upload_date': video_upload_date, 'upload_date': video_upload_date,
'title': self._og_search_title(webpage), 'title': title,
'subtitles': video_subtitles, 'subtitles': video_subtitles,
'thumbnail': info['thumbnail_url'], 'thumbnail': info['thumbnail_url'],
'age_limit': age_limit, 'age_limit': age_limit,
'view_count': view_count, 'view_count': view_count,

View File

@ -11,10 +11,10 @@ from ..utils import (
class DaumIE(InfoExtractor): class DaumIE(InfoExtractor):
_VALID_URL = r'https?://(?:m\.)?tvpot\.daum\.net/.*?clipid=(?P<id>\d+)' _VALID_URL = r'https?://(?:m\.)?tvpot\.daum\.net/(?:v/|.*?clipid=)(?P<id>[^?#&]+)'
IE_NAME = 'daum.net' IE_NAME = 'daum.net'
_TEST = { _TESTS = [{
'url': 'http://tvpot.daum.net/clip/ClipView.do?clipid=52554690', 'url': 'http://tvpot.daum.net/clip/ClipView.do?clipid=52554690',
'info_dict': { 'info_dict': {
'id': '52554690', 'id': '52554690',
@ -24,11 +24,17 @@ class DaumIE(InfoExtractor):
'upload_date': '20130831', 'upload_date': '20130831',
'duration': 3868, 'duration': 3868,
}, },
} }, {
'url': 'http://tvpot.daum.net/v/vab4dyeDBysyBssyukBUjBz',
'only_matching': True,
}, {
'url': 'http://tvpot.daum.net/v/07dXWRka62Y%24',
'only_matching': True,
}]
def _real_extract(self, url): def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
video_id = mobj.group(1) video_id = mobj.group('id')
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(
@ -42,7 +48,6 @@ class DaumIE(InfoExtractor):
'http://videofarm.daum.net/controller/api/open/v1_2/MovieData.apixml?' + query, 'http://videofarm.daum.net/controller/api/open/v1_2/MovieData.apixml?' + query,
video_id, 'Downloading video formats info') video_id, 'Downloading video formats info')
self.to_screen(u'%s: Getting video urls' % video_id)
formats = [] formats = []
for format_el in urls.findall('result/output_list/output_list'): for format_el in urls.findall('result/output_list/output_list'):
profile = format_el.attrib['profile'] profile = format_el.attrib['profile']
@ -52,7 +57,7 @@ class DaumIE(InfoExtractor):
}) })
url_doc = self._download_xml( url_doc = self._download_xml(
'http://videofarm.daum.net/controller/api/open/v1_2/MovieLocation.apixml?' + format_query, 'http://videofarm.daum.net/controller/api/open/v1_2/MovieLocation.apixml?' + format_query,
video_id, note=False) video_id, note='Downloading video data for %s format' % profile)
format_url = url_doc.find('result/url').text format_url = url_doc.find('result/url').text
formats.append({ formats.append({
'url': format_url, 'url': format_url,

View File

@ -7,7 +7,7 @@ class DivxStageIE(NovaMovIE):
IE_NAME = 'divxstage' IE_NAME = 'divxstage'
IE_DESC = 'DivxStage' IE_DESC = 'DivxStage'
_VALID_URL = NovaMovIE._VALID_URL_TEMPLATE % {'host': 'divxstage\.(?:eu|net|ch|co|at|ag)'} _VALID_URL = NovaMovIE._VALID_URL_TEMPLATE % {'host': 'divxstage\.(?:eu|net|ch|co|at|ag|to)'}
_HOST = 'www.divxstage.eu' _HOST = 'www.divxstage.eu'

View File

@ -5,28 +5,32 @@ import os.path
import re import re
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import compat_urllib_parse_unquote from ..utils import compat_urllib_parse_unquote, url_basename
class DropboxIE(InfoExtractor): class DropboxIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?dropbox[.]com/s/(?P<id>[a-zA-Z0-9]{15})/(?P<title>[^?#]*)' _VALID_URL = r'https?://(?:www\.)?dropbox[.]com/sh?/(?P<id>[a-zA-Z0-9]{15})/.*'
_TEST = { _TESTS = [{
'url': 'https://www.dropbox.com/s/nelirfsxnmcfbfh/youtube-dl%20test%20video%20%27%C3%A4%22BaW_jenozKc.mp4?dl=0', 'url': 'https://www.dropbox.com/s/nelirfsxnmcfbfh/youtube-dl%20test%20video%20%27%C3%A4%22BaW_jenozKc.mp4?dl=0',
'info_dict': { 'info_dict': {
'id': 'nelirfsxnmcfbfh', 'id': 'nelirfsxnmcfbfh',
'ext': 'mp4', 'ext': 'mp4',
'title': 'youtube-dl test video \'ä"BaW_jenozKc' 'title': 'youtube-dl test video \'ä"BaW_jenozKc'
} }
} },
{
'url': 'https://www.dropbox.com/sh/662glsejgzoj9sr/AAByil3FGH9KFNZ13e08eSa1a/Pregame%20Ceremony%20Program%20PA%2020140518.m4v',
'only_matching': True,
},
]
def _real_extract(self, url): def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
video_id = mobj.group('id') video_id = mobj.group('id')
fn = compat_urllib_parse_unquote(mobj.group('title')) fn = compat_urllib_parse_unquote(url_basename(url))
title = os.path.splitext(fn)[0] title = os.path.splitext(fn)[0]
video_url = ( video_url = re.sub(r'[?&]dl=0', '', url)
re.sub(r'[?&]dl=0', '', url) + video_url += ('?' if '?' not in video_url else '&') + 'dl=1'
('?' if '?' in url else '&') + 'dl=1')
return { return {
'id': video_id, 'id': video_id,

View File

@ -19,7 +19,7 @@ class DrTuberIE(InfoExtractor):
'like_count': int, 'like_count': int,
'dislike_count': int, 'dislike_count': int,
'comment_count': int, 'comment_count': int,
'categories': list, # NSFW 'categories': ['Babe', 'Blonde', 'Erotic', 'Outdoor', 'Softcore', 'Solo'],
'thumbnail': 're:https?://.*\.jpg$', 'thumbnail': 're:https?://.*\.jpg$',
'age_limit': 18, 'age_limit': 18,
} }
@ -52,9 +52,9 @@ class DrTuberIE(InfoExtractor):
r'<span class="comments_count">([\d,\.]+)</span>', r'<span class="comments_count">([\d,\.]+)</span>',
webpage, 'comment count', fatal=False)) webpage, 'comment count', fatal=False))
cats_str = self._html_search_regex( cats_str = self._search_regex(
r'<meta name="keywords" content="([^"]+)"', webpage, 'categories', fatal=False) r'<span>Categories:</span><div>(.+?)</div>', webpage, 'categories', fatal=False)
categories = None if cats_str is None else cats_str.split(' ') categories = [] if not cats_str else re.findall(r'<a title="([^"]+)"', cats_str)
return { return {
'id': video_id, 'id': video_id,

View File

@ -1,7 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import re
from .subtitles import SubtitlesInfoExtractor from .subtitles import SubtitlesInfoExtractor
from .common import ExtractorError from .common import ExtractorError
from ..utils import parse_iso8601 from ..utils import parse_iso8601
@ -25,8 +23,7 @@ class DRTVIE(SubtitlesInfoExtractor):
} }
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')
programcard = self._download_json( programcard = self._download_json(
'http://www.dr.dk/mu/programcard/expanded/%s' % video_id, video_id, 'Downloading video JSON') 'http://www.dr.dk/mu/programcard/expanded/%s' % video_id, video_id, 'Downloading video JSON')
@ -35,7 +32,7 @@ class DRTVIE(SubtitlesInfoExtractor):
title = data['Title'] title = data['Title']
description = data['Description'] description = data['Description']
timestamp = parse_iso8601(data['CreatedTime'][:-5]) timestamp = parse_iso8601(data['CreatedTime'])
thumbnail = None thumbnail = None
duration = None duration = None

View File

@ -0,0 +1,61 @@
# coding: utf-8
from __future__ import unicode_literals
import re
from .common import InfoExtractor
class EinthusanIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?einthusan\.com/movies/watch.php\?([^#]*?)id=(?P<id>[0-9]+)'
_TESTS = [
{
'url': 'http://www.einthusan.com/movies/watch.php?id=2447',
'md5': 'af244f4458cd667205e513d75da5b8b1',
'info_dict': {
'id': '2447',
'ext': 'mp4',
'title': 'Ek Villain',
'thumbnail': 're:^https?://.*\.jpg$',
'description': 'md5:9d29fc91a7abadd4591fb862fa560d93',
}
},
{
'url': 'http://www.einthusan.com/movies/watch.php?id=1671',
'md5': 'ef63c7a803e22315880ed182c10d1c5c',
'info_dict': {
'id': '1671',
'ext': 'mp4',
'title': 'Soodhu Kavvuum',
'thumbnail': 're:^https?://.*\.jpg$',
'description': 'md5:05d8a0c0281a4240d86d76e14f2f4d51',
}
},
]
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
video_id = mobj.group('id')
webpage = self._download_webpage(url, video_id)
video_title = self._html_search_regex(
r'<h1><a class="movie-title".*?>(.*?)</a></h1>', webpage, 'title')
video_url = self._html_search_regex(
r'''(?s)jwplayer\("mediaplayer"\)\.setup\({.*?'file': '([^']+)'.*?}\);''',
webpage, 'video url')
description = self._html_search_meta('description', webpage)
thumbnail = self._html_search_regex(
r'''<a class="movie-cover-wrapper".*?><img src=["'](.*?)["'].*?/></a>''',
webpage, "thumbnail url", fatal=False)
if thumbnail is not None:
thumbnail = thumbnail.replace('..', 'http://www.einthusan.com')
return {
'id': video_id,
'title': video_title,
'url': video_url,
'thumbnail': thumbnail,
'description': description,
}

View File

@ -1,4 +1,6 @@
# encoding: utf-8 # encoding: utf-8
from __future__ import unicode_literals
import re import re
from .common import InfoExtractor from .common import InfoExtractor
@ -7,20 +9,20 @@ from ..utils import ExtractorError
class EitbIE(InfoExtractor): class EitbIE(InfoExtractor):
IE_NAME = u'eitb.tv' IE_NAME = 'eitb.tv'
_VALID_URL = r'https?://www\.eitb\.tv/(eu/bideoa|es/video)/[^/]+/(?P<playlist_id>\d+)/(?P<chapter_id>\d+)' _VALID_URL = r'https?://www\.eitb\.tv/(eu/bideoa|es/video)/[^/]+/(?P<playlist_id>\d+)/(?P<chapter_id>\d+)'
_TEST = { _TEST = {
u'add_ie': ['Brightcove'], 'add_ie': ['Brightcove'],
u'url': u'http://www.eitb.tv/es/video/60-minutos-60-minutos-2013-2014/2677100210001/2743577154001/lasa-y-zabala-30-anos/', 'url': 'http://www.eitb.tv/es/video/60-minutos-60-minutos-2013-2014/2677100210001/2743577154001/lasa-y-zabala-30-anos/',
u'md5': u'edf4436247185adee3ea18ce64c47998', 'md5': 'edf4436247185adee3ea18ce64c47998',
u'info_dict': { 'info_dict': {
u'id': u'2743577154001', 'id': '2743577154001',
u'ext': u'mp4', 'ext': 'mp4',
u'title': u'60 minutos (Lasa y Zabala, 30 años)', 'title': '60 minutos (Lasa y Zabala, 30 años)',
# All videos from eitb has this description in the brightcove info # All videos from eitb has this description in the brightcove info
u'description': u'.', 'description': '.',
u'uploader': u'Euskal Telebista', 'uploader': 'Euskal Telebista',
}, },
} }
@ -30,7 +32,7 @@ class EitbIE(InfoExtractor):
webpage = self._download_webpage(url, chapter_id) webpage = self._download_webpage(url, chapter_id)
bc_url = BrightcoveIE._extract_brightcove_url(webpage) bc_url = BrightcoveIE._extract_brightcove_url(webpage)
if bc_url is None: if bc_url is None:
raise ExtractorError(u'Could not extract the Brightcove url') raise ExtractorError('Could not extract the Brightcove url')
# The BrightcoveExperience object doesn't contain the video id, we set # The BrightcoveExperience object doesn't contain the video id, we set
# it manually # it manually
bc_url += '&%40videoPlayer={0}'.format(chapter_id) bc_url += '&%40videoPlayer={0}'.format(chapter_id)

View File

@ -14,11 +14,11 @@ class EpornerIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?eporner\.com/hd-porn/(?P<id>\d+)/(?P<display_id>[\w-]+)' _VALID_URL = r'https?://(?:www\.)?eporner\.com/hd-porn/(?P<id>\d+)/(?P<display_id>[\w-]+)'
_TEST = { _TEST = {
'url': 'http://www.eporner.com/hd-porn/95008/Infamous-Tiffany-Teen-Strip-Tease-Video/', 'url': 'http://www.eporner.com/hd-porn/95008/Infamous-Tiffany-Teen-Strip-Tease-Video/',
'md5': '3b427ae4b9d60619106de3185c2987cd', 'md5': '39d486f046212d8e1b911c52ab4691f8',
'info_dict': { 'info_dict': {
'id': '95008', 'id': '95008',
'display_id': 'Infamous-Tiffany-Teen-Strip-Tease-Video', 'display_id': 'Infamous-Tiffany-Teen-Strip-Tease-Video',
'ext': 'flv', 'ext': 'mp4',
'title': 'Infamous Tiffany Teen Strip Tease Video', 'title': 'Infamous Tiffany Teen Strip Tease Video',
'duration': 194, 'duration': 194,
'view_count': int, 'view_count': int,

View File

@ -7,6 +7,7 @@ from ..utils import (
compat_urllib_parse_urlparse, compat_urllib_parse_urlparse,
compat_urllib_request, compat_urllib_request,
compat_urllib_parse, compat_urllib_parse,
str_to_int,
) )
@ -20,6 +21,7 @@ class ExtremeTubeIE(InfoExtractor):
'ext': 'mp4', 'ext': 'mp4',
'title': 'Music Video 14 british euro brit european cumshots swallow', 'title': 'Music Video 14 british euro brit european cumshots swallow',
'uploader': 'unknown', 'uploader': 'unknown',
'view_count': int,
'age_limit': 18, 'age_limit': 18,
} }
}, { }, {
@ -39,8 +41,12 @@ class ExtremeTubeIE(InfoExtractor):
video_title = self._html_search_regex( video_title = self._html_search_regex(
r'<h1 [^>]*?title="([^"]+)"[^>]*>', webpage, 'title') r'<h1 [^>]*?title="([^"]+)"[^>]*>', webpage, 'title')
uploader = self._html_search_regex( uploader = self._html_search_regex(
r'>Posted by:(?=<)(?:\s|<[^>]*>)*(.+?)\|', webpage, 'uploader', r'Uploaded by:\s*</strong>\s*(.+?)\s*</div>',
fatal=False) webpage, 'uploader', fatal=False)
view_count = str_to_int(self._html_search_regex(
r'Views:\s*</strong>\s*<span>([\d,\.]+)</span>',
webpage, 'view count', fatal=False))
video_url = compat_urllib_parse.unquote(self._html_search_regex( video_url = compat_urllib_parse.unquote(self._html_search_regex(
r'video_url=(.+?)&amp;', webpage, 'video_url')) r'video_url=(.+?)&amp;', webpage, 'video_url'))
path = compat_urllib_parse_urlparse(video_url).path path = compat_urllib_parse_urlparse(video_url).path
@ -51,6 +57,7 @@ class ExtremeTubeIE(InfoExtractor):
'id': video_id, 'id': video_id,
'title': video_title, 'title': video_title,
'uploader': uploader, 'uploader': uploader,
'view_count': view_count,
'url': video_url, 'url': video_url,
'format': format, 'format': format,
'format_id': format, 'format_id': format,

View File

@ -12,8 +12,8 @@ from ..utils import (
compat_urllib_parse, compat_urllib_parse,
compat_urllib_request, compat_urllib_request,
urlencode_postdata, urlencode_postdata,
ExtractorError, ExtractorError,
limit_length,
) )
@ -35,7 +35,15 @@ class FacebookIE(InfoExtractor):
'id': '637842556329505', 'id': '637842556329505',
'ext': 'mp4', 'ext': 'mp4',
'duration': 38, 'duration': 38,
'title': 'Did you know Kei Nishikori is the first Asian man to ever reach a Grand Slam fin...', 'title': 're:Did you know Kei Nishikori is the first Asian man to ever reach a Grand Slam',
}
}, {
'note': 'Video without discernible title',
'url': 'https://www.facebook.com/video.php?v=274175099429670',
'info_dict': {
'id': '274175099429670',
'ext': 'mp4',
'title': 'Facebook video #274175099429670',
} }
}, { }, {
'url': 'https://www.facebook.com/video.php?v=10204634152394104', 'url': 'https://www.facebook.com/video.php?v=10204634152394104',
@ -131,8 +139,7 @@ class FacebookIE(InfoExtractor):
video_title = self._html_search_regex( video_title = self._html_search_regex(
r'(?s)<span class="fbPhotosPhotoCaption".*?id="fbPhotoPageCaption"><span class="hasCaption">(.*?)</span>', r'(?s)<span class="fbPhotosPhotoCaption".*?id="fbPhotoPageCaption"><span class="hasCaption">(.*?)</span>',
webpage, 'alternative title', default=None) webpage, 'alternative title', default=None)
if len(video_title) > 80 + 3: video_title = limit_length(video_title, 80)
video_title = video_title[:80] + '...'
if not video_title: if not video_title:
video_title = 'Facebook video #%s' % video_id video_title = 'Facebook video #%s' % video_id

View File

@ -1,49 +1,48 @@
# encoding: utf-8 # encoding: utf-8
import re from __future__ import unicode_literals
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import (
determine_ext,
)
class FazIE(InfoExtractor): class FazIE(InfoExtractor):
IE_NAME = u'faz.net' IE_NAME = 'faz.net'
_VALID_URL = r'https?://www\.faz\.net/multimedia/videos/.*?-(?P<id>\d+)\.html' _VALID_URL = r'https?://www\.faz\.net/multimedia/videos/.*?-(?P<id>\d+)\.html'
_TEST = { _TEST = {
u'url': u'http://www.faz.net/multimedia/videos/stockholm-chemie-nobelpreis-fuer-drei-amerikanische-forscher-12610585.html', 'url': 'http://www.faz.net/multimedia/videos/stockholm-chemie-nobelpreis-fuer-drei-amerikanische-forscher-12610585.html',
u'file': u'12610585.mp4', 'info_dict': {
u'info_dict': { 'id': '12610585',
u'title': u'Stockholm: Chemie-Nobelpreis für drei amerikanische Forscher', 'ext': 'mp4',
u'description': u'md5:1453fbf9a0d041d985a47306192ea253', 'title': 'Stockholm: Chemie-Nobelpreis für drei amerikanische Forscher',
'description': 'md5:1453fbf9a0d041d985a47306192ea253',
}, },
} }
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')
self.to_screen(video_id)
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
config_xml_url = self._search_regex(r'writeFLV\(\'(.+?)\',', webpage, config_xml_url = self._search_regex(
u'config xml url') r'writeFLV\(\'(.+?)\',', webpage, 'config xml url')
config = self._download_xml(config_xml_url, video_id, config = self._download_xml(
u'Downloading config xml') config_xml_url, video_id, 'Downloading config xml')
encodings = config.find('ENCODINGS') encodings = config.find('ENCODINGS')
formats = [] formats = []
for code in ['LOW', 'HIGH', 'HQ']: for pref, code in enumerate(['LOW', 'HIGH', 'HQ']):
encoding = encodings.find(code) encoding = encodings.find(code)
if encoding is None: if encoding is None:
continue continue
encoding_url = encoding.find('FILENAME').text encoding_url = encoding.find('FILENAME').text
formats.append({ formats.append({
'url': encoding_url, 'url': encoding_url,
'ext': determine_ext(encoding_url),
'format_id': code.lower(), 'format_id': code.lower(),
'quality': pref,
}) })
self._sort_formats(formats)
descr = self._html_search_regex(r'<p class="Content Copy">(.*?)</p>', webpage, u'description') descr = self._html_search_regex(
r'<p class="Content Copy">(.*?)</p>', webpage, 'description', fatal=False)
return { return {
'id': video_id, 'id': video_id,
'title': self._og_search_title(webpage), 'title': self._og_search_title(webpage),

View File

@ -1,25 +1,27 @@
from __future__ import unicode_literals
import re import re
import random import random
import json import json
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import ( from ..utils import (
determine_ext,
get_element_by_id, get_element_by_id,
clean_html, clean_html,
) )
class FKTVIE(InfoExtractor): class FKTVIE(InfoExtractor):
IE_NAME = u'fernsehkritik.tv' IE_NAME = 'fernsehkritik.tv'
_VALID_URL = r'(?:http://)?(?:www\.)?fernsehkritik\.tv/folge-(?P<ep>[0-9]+)(?:/.*)?' _VALID_URL = r'http://(?:www\.)?fernsehkritik\.tv/folge-(?P<ep>[0-9]+)(?:/.*)?'
_TEST = { _TEST = {
u'url': u'http://fernsehkritik.tv/folge-1', 'url': 'http://fernsehkritik.tv/folge-1',
u'file': u'00011.flv', 'info_dict': {
u'info_dict': { 'id': '00011',
u'title': u'Folge 1 vom 10. April 2007', 'ext': 'flv',
u'description': u'md5:fb4818139c7cfe6907d4b83412a6864f', 'title': 'Folge 1 vom 10. April 2007',
'description': 'md5:fb4818139c7cfe6907d4b83412a6864f',
}, },
} }
@ -32,7 +34,7 @@ class FKTVIE(InfoExtractor):
start_webpage = self._download_webpage('http://fernsehkritik.tv/folge-%d/Start' % episode, start_webpage = self._download_webpage('http://fernsehkritik.tv/folge-%d/Start' % episode,
episode) episode)
playlist = self._search_regex(r'playlist = (\[.*?\]);', start_webpage, playlist = self._search_regex(r'playlist = (\[.*?\]);', start_webpage,
u'playlist', flags=re.DOTALL) 'playlist', flags=re.DOTALL)
files = json.loads(re.sub('{[^{}]*?}', '{}', playlist)) files = json.loads(re.sub('{[^{}]*?}', '{}', playlist))
# TODO: return a single multipart video # TODO: return a single multipart video
videos = [] videos = []
@ -42,7 +44,6 @@ class FKTVIE(InfoExtractor):
videos.append({ videos.append({
'id': video_id, 'id': video_id,
'url': video_url, 'url': video_url,
'ext': determine_ext(video_url),
'title': clean_html(get_element_by_id('eptitle', start_webpage)), 'title': clean_html(get_element_by_id('eptitle', start_webpage)),
'description': clean_html(get_element_by_id('contentlist', start_webpage)), 'description': clean_html(get_element_by_id('contentlist', start_webpage)),
'thumbnail': video_thumbnail 'thumbnail': video_thumbnail
@ -51,14 +52,15 @@ class FKTVIE(InfoExtractor):
class FKTVPosteckeIE(InfoExtractor): class FKTVPosteckeIE(InfoExtractor):
IE_NAME = u'fernsehkritik.tv:postecke' IE_NAME = 'fernsehkritik.tv:postecke'
_VALID_URL = r'(?:http://)?(?:www\.)?fernsehkritik\.tv/inline-video/postecke\.php\?(.*&)?ep=(?P<ep>[0-9]+)(&|$)' _VALID_URL = r'http://(?:www\.)?fernsehkritik\.tv/inline-video/postecke\.php\?(.*&)?ep=(?P<ep>[0-9]+)(&|$)'
_TEST = { _TEST = {
u'url': u'http://fernsehkritik.tv/inline-video/postecke.php?iframe=true&width=625&height=440&ep=120', 'url': 'http://fernsehkritik.tv/inline-video/postecke.php?iframe=true&width=625&height=440&ep=120',
u'file': u'0120.flv', 'md5': '262f0adbac80317412f7e57b4808e5c4',
u'md5': u'262f0adbac80317412f7e57b4808e5c4', 'info_dict': {
u'info_dict': { 'id': '0120',
u"title": u"Postecke 120" 'ext': 'flv',
'title': 'Postecke 120',
} }
} }
@ -71,8 +73,7 @@ class FKTVPosteckeIE(InfoExtractor):
video_url = 'http://dl%d.fernsehkritik.tv/postecke/postecke%d.flv' % (server, episode) video_url = 'http://dl%d.fernsehkritik.tv/postecke/postecke%d.flv' % (server, episode)
video_title = 'Postecke %d' % episode video_title = 'Postecke %d' % episode
return { return {
'id': video_id, 'id': video_id,
'url': video_url, 'url': video_url,
'ext': determine_ext(video_url), 'title': video_title,
'title': video_title,
} }

View File

@ -10,13 +10,13 @@ from ..utils import (
class FlickrIE(InfoExtractor): class FlickrIE(InfoExtractor):
"""Information Extractor for Flickr videos""" _VALID_URL = r'https?://(?:www\.|secure\.)?flickr\.com/photos/(?P<uploader_id>[\w\-_@]+)/(?P<id>\d+).*'
_VALID_URL = r'(?:https?://)?(?:www\.|secure\.)?flickr\.com/photos/(?P<uploader_id>[\w\-_@]+)/(?P<id>\d+).*'
_TEST = { _TEST = {
'url': 'http://www.flickr.com/photos/forestwander-nature-pictures/5645318632/in/photostream/', 'url': 'http://www.flickr.com/photos/forestwander-nature-pictures/5645318632/in/photostream/',
'file': '5645318632.mp4',
'md5': '6fdc01adbc89d72fc9c4f15b4a4ba87b', 'md5': '6fdc01adbc89d72fc9c4f15b4a4ba87b',
'info_dict': { 'info_dict': {
'id': '5645318632',
'ext': 'mp4',
"description": "Waterfalls in the Springtime at Dark Hollow Waterfalls. These are located just off of Skyline Drive in Virginia. They are only about 6/10 of a mile hike but it is a pretty steep hill and a good climb back up.", "description": "Waterfalls in the Springtime at Dark Hollow Waterfalls. These are located just off of Skyline Drive in Virginia. They are only about 6/10 of a mile hike but it is a pretty steep hill and a good climb back up.",
"uploader_id": "forestwander-nature-pictures", "uploader_id": "forestwander-nature-pictures",
"title": "Dark Hollow Waterfalls" "title": "Dark Hollow Waterfalls"
@ -49,12 +49,12 @@ class FlickrIE(InfoExtractor):
raise ExtractorError('Unable to extract video url') raise ExtractorError('Unable to extract video url')
video_url = mobj.group(1) + unescapeHTML(mobj.group(2)) video_url = mobj.group(1) + unescapeHTML(mobj.group(2))
return [{ return {
'id': video_id, 'id': video_id,
'url': video_url, 'url': video_url,
'ext': 'mp4', 'ext': 'mp4',
'title': self._og_search_title(webpage), 'title': self._og_search_title(webpage),
'description': self._og_search_description(webpage), 'description': self._og_search_description(webpage),
'thumbnail': self._og_search_thumbnail(webpage), 'thumbnail': self._og_search_thumbnail(webpage),
'uploader_id': video_uploader_id, 'uploader_id': video_uploader_id,
}] }

View File

@ -4,16 +4,21 @@ from __future__ import unicode_literals
import re import re
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import int_or_none
class FranceInterIE(InfoExtractor): class FranceInterIE(InfoExtractor):
_VALID_URL = r'http://(?:www\.)?franceinter\.fr/player/reecouter\?play=(?P<id>[0-9]{6})' _VALID_URL = r'http://(?:www\.)?franceinter\.fr/player/reecouter\?play=(?P<id>[0-9]+)'
_TEST = { _TEST = {
'url': 'http://www.franceinter.fr/player/reecouter?play=793962', 'url': 'http://www.franceinter.fr/player/reecouter?play=793962',
'file': '793962.mp3',
'md5': '4764932e466e6f6c79c317d2e74f6884', 'md5': '4764932e466e6f6c79c317d2e74f6884',
"info_dict": { "info_dict": {
"title": "LHistoire dans les jeux vidéo", 'id': '793962',
'ext': 'mp3',
'title': 'LHistoire dans les jeux vidéo',
'description': 'md5:7e93ddb4451e7530022792240a3049c7',
'timestamp': 1387369800,
'upload_date': '20131218',
}, },
} }
@ -22,17 +27,26 @@ class FranceInterIE(InfoExtractor):
video_id = mobj.group('id') video_id = mobj.group('id')
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
title = self._html_search_regex(
r'<span class="roll_overflow">(.*?)</span></h1>', webpage, 'title')
path = self._search_regex( path = self._search_regex(
r'&urlAOD=(.*?)&startTime', webpage, 'video url') r'<a id="player".+?href="([^"]+)"', webpage, 'video url')
video_url = 'http://www.franceinter.fr/' + path video_url = 'http://www.franceinter.fr/' + path
title = self._html_search_regex(
r'<span class="title">(.+?)</span>', webpage, 'title')
description = self._html_search_regex(
r'<span class="description">(.*?)</span>',
webpage, 'description', fatal=False)
timestamp = int_or_none(self._search_regex(
r'data-date="(\d+)"', webpage, 'upload date', fatal=False))
return { return {
'id': video_id, 'id': video_id,
'title': title,
'description': description,
'timestamp': timestamp,
'formats': [{ 'formats': [{
'url': video_url, 'url': video_url,
'vcodec': 'none', 'vcodec': 'none',
}], }],
'title': title,
} }

View File

@ -8,45 +8,68 @@ import json
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import ( from ..utils import (
compat_urlparse, compat_urlparse,
ExtractorError,
clean_html,
parse_duration,
compat_urllib_parse_urlparse,
int_or_none,
) )
class FranceTVBaseInfoExtractor(InfoExtractor): class FranceTVBaseInfoExtractor(InfoExtractor):
def _extract_video(self, video_id): def _extract_video(self, video_id, catalogue):
info = self._download_xml( info = self._download_json(
'http://www.francetvinfo.fr/appftv/webservices/video/' 'http://webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/?idDiffusion=%s&catalogue=%s'
'getInfosOeuvre.php?id-diffusion=' % (video_id, catalogue),
+ video_id, video_id, 'Downloading XML config') video_id, 'Downloading video JSON')
manifest_url = info.find('videos/video/url').text if info.get('status') == 'NOK':
manifest_url = manifest_url.replace('/z/', '/i/') raise ExtractorError(
'%s returned error: %s' % (self.IE_NAME, info['message']), expected=True)
if manifest_url.startswith('rtmp'): formats = []
formats = [{'url': manifest_url, 'ext': 'flv'}] for video in info['videos']:
else: if video['statut'] != 'ONLINE':
formats = [] continue
available_formats = self._search_regex(r'/[^,]*,(.*?),k\.mp4', manifest_url, 'available formats') video_url = video['url']
for index, format_descr in enumerate(available_formats.split(',')): if not video_url:
format_info = { continue
'url': manifest_url.replace('manifest.f4m', 'index_%d_av.m3u8' % index), format_id = video['format']
'ext': 'mp4', if video_url.endswith('.f4m'):
} video_url_parsed = compat_urllib_parse_urlparse(video_url)
m_resolution = re.search(r'(?P<width>\d+)x(?P<height>\d+)', format_descr) f4m_url = self._download_webpage(
if m_resolution is not None: 'http://hdfauth.francetv.fr/esi/urltokengen2.html?url=%s' % video_url_parsed.path,
format_info.update({ video_id, 'Downloading f4m manifest token', fatal=False)
'width': int(m_resolution.group('width')), if f4m_url:
'height': int(m_resolution.group('height')), f4m_formats = self._extract_f4m_formats(f4m_url, video_id)
}) for f4m_format in f4m_formats:
formats.append(format_info) f4m_format['preference'] = 1
formats.extend(f4m_formats)
thumbnail_path = info.find('image').text elif video_url.endswith('.m3u8'):
formats.extend(self._extract_m3u8_formats(video_url, video_id, 'mp4'))
elif video_url.startswith('rtmp'):
formats.append({
'url': video_url,
'format_id': 'rtmp-%s' % format_id,
'ext': 'flv',
'preference': 1,
})
else:
formats.append({
'url': video_url,
'format_id': format_id,
'preference': -1,
})
self._sort_formats(formats)
return { return {
'id': video_id, 'id': video_id,
'title': info.find('titre').text, 'title': info['titre'],
'description': clean_html(info['synopsis']),
'thumbnail': compat_urlparse.urljoin('http://pluzz.francetv.fr', info['image']),
'duration': parse_duration(info['duree']),
'timestamp': int_or_none(info['diffusion']['timestamp']),
'formats': formats, 'formats': formats,
'thumbnail': compat_urlparse.urljoin('http://pluzz.francetv.fr', thumbnail_path),
'description': info.find('synopsis').text,
} }
@ -61,7 +84,7 @@ class PluzzIE(FranceTVBaseInfoExtractor):
webpage = self._download_webpage(url, title) webpage = self._download_webpage(url, title)
video_id = self._search_regex( video_id = self._search_regex(
r'data-diffusion="(\d+)"', webpage, 'ID') r'data-diffusion="(\d+)"', webpage, 'ID')
return self._extract_video(video_id) return self._extract_video(video_id, 'Pluzz')
class FranceTvInfoIE(FranceTVBaseInfoExtractor): class FranceTvInfoIE(FranceTVBaseInfoExtractor):
@ -72,11 +95,10 @@ class FranceTvInfoIE(FranceTVBaseInfoExtractor):
'url': 'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html', 'url': 'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html',
'info_dict': { 'info_dict': {
'id': '84981923', 'id': '84981923',
'ext': 'mp4', 'ext': 'flv',
'title': 'Soir 3', 'title': 'Soir 3',
}, 'upload_date': '20130826',
'params': { 'timestamp': 1377548400,
'skip_download': True,
}, },
}, { }, {
'url': 'http://www.francetvinfo.fr/elections/europeennes/direct-europeennes-regardez-le-debat-entre-les-candidats-a-la-presidence-de-la-commission_600639.html', 'url': 'http://www.francetvinfo.fr/elections/europeennes/direct-europeennes-regardez-le-debat-entre-les-candidats-a-la-presidence-de-la-commission_600639.html',
@ -88,15 +110,17 @@ class FranceTvInfoIE(FranceTVBaseInfoExtractor):
}, },
'params': { 'params': {
'skip_download': 'HLS (reqires ffmpeg)' 'skip_download': 'HLS (reqires ffmpeg)'
} },
'skip': 'Ce direct est terminé et sera disponible en rattrapage dans quelques minutes.',
}] }]
def _real_extract(self, url): def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
page_title = mobj.group('title') page_title = mobj.group('title')
webpage = self._download_webpage(url, page_title) webpage = self._download_webpage(url, page_title)
video_id = self._search_regex(r'id-video=((?:[^0-9]*?_)?[0-9]+)[@"]', webpage, 'video id') video_id, catalogue = self._search_regex(
return self._extract_video(video_id) r'id-video=([^@]+@[^"]+)', webpage, 'video id').split('@')
return self._extract_video(video_id, catalogue)
class FranceTVIE(FranceTVBaseInfoExtractor): class FranceTVIE(FranceTVBaseInfoExtractor):
@ -112,91 +136,77 @@ class FranceTVIE(FranceTVBaseInfoExtractor):
# france2 # france2
{ {
'url': 'http://www.france2.fr/emissions/13h15-le-samedi-le-dimanche/videos/75540104', 'url': 'http://www.france2.fr/emissions/13h15-le-samedi-le-dimanche/videos/75540104',
'file': '75540104.mp4', 'md5': 'c03fc87cb85429ffd55df32b9fc05523',
'info_dict': { 'info_dict': {
'title': '13h15, le samedi...', 'id': '109169362',
'description': 'md5:2e5b58ba7a2d3692b35c792be081a03d', 'ext': 'flv',
}, 'title': '13h15, le dimanche...',
'params': { 'description': 'md5:9a0932bb465f22d377a449be9d1a0ff7',
# m3u8 download 'upload_date': '20140914',
'skip_download': True, 'timestamp': 1410693600,
}, },
}, },
# france3 # france3
{ {
'url': 'http://www.france3.fr/emissions/pieces-a-conviction/diffusions/13-11-2013_145575', 'url': 'http://www.france3.fr/emissions/pieces-a-conviction/diffusions/13-11-2013_145575',
'md5': '679bb8f8921f8623bd658fa2f8364da0',
'info_dict': { 'info_dict': {
'id': '000702326_CAPP_PicesconvictionExtrait313022013_120220131722_Au', 'id': '000702326_CAPP_PicesconvictionExtrait313022013_120220131722_Au',
'ext': 'flv', 'ext': 'mp4',
'title': 'Le scandale du prix des médicaments', 'title': 'Le scandale du prix des médicaments',
'description': 'md5:1384089fbee2f04fc6c9de025ee2e9ce', 'description': 'md5:1384089fbee2f04fc6c9de025ee2e9ce',
}, 'upload_date': '20131113',
'params': { 'timestamp': 1384380000,
# rtmp download
'skip_download': True,
}, },
}, },
# france4 # france4
{ {
'url': 'http://www.france4.fr/emissions/hero-corp/videos/rhozet_herocorp_bonus_1_20131106_1923_06112013172108_F4', 'url': 'http://www.france4.fr/emissions/hero-corp/videos/rhozet_herocorp_bonus_1_20131106_1923_06112013172108_F4',
'md5': 'a182bf8d2c43d88d46ec48fbdd260c1c',
'info_dict': { 'info_dict': {
'id': 'rhozet_herocorp_bonus_1_20131106_1923_06112013172108_F4', 'id': 'rhozet_herocorp_bonus_1_20131106_1923_06112013172108_F4',
'ext': 'flv', 'ext': 'mp4',
'title': 'Hero Corp Making of - Extrait 1', 'title': 'Hero Corp Making of - Extrait 1',
'description': 'md5:c87d54871b1790679aec1197e73d650a', 'description': 'md5:c87d54871b1790679aec1197e73d650a',
}, 'upload_date': '20131106',
'params': { 'timestamp': 1383766500,
# rtmp download
'skip_download': True,
}, },
}, },
# france5 # france5
{ {
'url': 'http://www.france5.fr/emissions/c-a-dire/videos/92837968', 'url': 'http://www.france5.fr/emissions/c-a-dire/videos/92837968',
'md5': '78f0f4064f9074438e660785bbf2c5d9',
'info_dict': { 'info_dict': {
'id': '92837968', 'id': '108961659',
'ext': 'mp4', 'ext': 'flv',
'title': 'C à dire ?!', 'title': 'C à dire ?!',
'description': 'md5:fb1db1cbad784dcce7c7a7bd177c8e2f', 'description': 'md5:1a4aeab476eb657bf57c4ff122129f81',
}, 'upload_date': '20140915',
'params': { 'timestamp': 1410795000,
# m3u8 download
'skip_download': True,
}, },
}, },
# franceo # franceo
{ {
'url': 'http://www.franceo.fr/jt/info-afrique/04-12-2013', 'url': 'http://www.franceo.fr/jt/info-afrique/04-12-2013',
'md5': '52f0bfe202848b15915a2f39aaa8981b',
'info_dict': { 'info_dict': {
'id': '92327925', 'id': '108634970',
'ext': 'mp4', 'ext': 'flv',
'title': 'Infô-Afrique', 'title': 'Infô Afrique',
'description': 'md5:ebf346da789428841bee0fd2a935ea55', 'description': 'md5:ebf346da789428841bee0fd2a935ea55',
'upload_date': '20140915',
'timestamp': 1410822000,
}, },
'params': {
# m3u8 download
'skip_download': True,
},
'skip': 'The id changes frequently',
}, },
] ]
def _real_extract(self, url): def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
if mobj.group('key'): webpage = self._download_webpage(url, mobj.group('key') or mobj.group('id'))
webpage = self._download_webpage(url, mobj.group('key')) video_id, catalogue = self._html_search_regex(
id_res = [ r'href="http://videos\.francetv\.fr/video/([^@]+@[^"]+)"',
(r'''(?x)<div\s+class="video-player">\s* webpage, 'video ID').split('@')
<a\s+href="http://videos.francetv.fr/video/([0-9]+)"\s+ return self._extract_video(video_id, catalogue)
class="francetv-video-player">'''),
(r'<a id="player_direct" href="http://info\.francetelevisions'
'\.fr/\?id-video=([^"/&]+)'),
(r'<a class="video" id="ftv_player_(.+?)"'),
]
video_id = self._html_search_regex(id_res, webpage, 'video ID')
else:
video_id = mobj.group('id')
return self._extract_video(video_id)
class GenerationQuoiIE(InfoExtractor): class GenerationQuoiIE(InfoExtractor):
@ -232,16 +242,15 @@ class CultureboxIE(FranceTVBaseInfoExtractor):
_VALID_URL = r'https?://(?:m\.)?culturebox\.francetvinfo\.fr/(?P<name>.*?)(\?|$)' _VALID_URL = r'https?://(?:m\.)?culturebox\.francetvinfo\.fr/(?P<name>.*?)(\?|$)'
_TEST = { _TEST = {
'url': 'http://culturebox.francetvinfo.fr/einstein-on-the-beach-au-theatre-du-chatelet-146813', 'url': 'http://culturebox.francetvinfo.fr/festivals/dans-les-jardins-de-william-christie/dans-les-jardins-de-william-christie-le-camus-162553',
'md5': '5ad6dec1ffb2a3fbcb20cc4b744be8d6',
'info_dict': { 'info_dict': {
'id': 'EV_6785', 'id': 'EV_22853',
'ext': 'mp4', 'ext': 'flv',
'title': 'Einstein on the beach au Théâtre du Châtelet', 'title': 'Dans les jardins de William Christie - Le Camus',
'description': 'md5:9ce2888b1efefc617b5e58b3f6200eeb', 'description': 'md5:4710c82315c40f0c865ca8b9a68b5299',
}, 'upload_date': '20140829',
'params': { 'timestamp': 1409317200,
# m3u8 download
'skip_download': True,
}, },
} }
@ -249,5 +258,7 @@ class CultureboxIE(FranceTVBaseInfoExtractor):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
name = mobj.group('name') name = mobj.group('name')
webpage = self._download_webpage(url, name) webpage = self._download_webpage(url, name)
video_id = self._search_regex(r'"http://videos\.francetv\.fr/video/(.*?)"', webpage, 'video id') video_id, catalogue = self._search_regex(
return self._extract_video(video_id) r'"http://videos\.francetv\.fr/video/([^@]+@[^"]+)"', webpage, 'video id').split('@')
return self._extract_video(video_id, catalogue)

View File

@ -8,7 +8,7 @@ from ..utils import ExtractorError
class FunnyOrDieIE(InfoExtractor): class FunnyOrDieIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?funnyordie\.com/(?P<type>embed|videos)/(?P<id>[0-9a-f]+)(?:$|[?#/])' _VALID_URL = r'https?://(?:www\.)?funnyordie\.com/(?P<type>embed|articles|videos)/(?P<id>[0-9a-f]+)(?:$|[?#/])'
_TESTS = [{ _TESTS = [{
'url': 'http://www.funnyordie.com/videos/0732f586d7/heart-shaped-box-literal-video-version', 'url': 'http://www.funnyordie.com/videos/0732f586d7/heart-shaped-box-literal-video-version',
'md5': 'bcd81e0c4f26189ee09be362ad6e6ba9', 'md5': 'bcd81e0c4f26189ee09be362ad6e6ba9',
@ -21,7 +21,7 @@ class FunnyOrDieIE(InfoExtractor):
}, },
}, { }, {
'url': 'http://www.funnyordie.com/embed/e402820827', 'url': 'http://www.funnyordie.com/embed/e402820827',
'md5': 'ff4d83318f89776ed0250634cfaa8d36', 'md5': '29f4c5e5a61ca39dfd7e8348a75d0aad',
'info_dict': { 'info_dict': {
'id': 'e402820827', 'id': 'e402820827',
'ext': 'mp4', 'ext': 'mp4',
@ -29,6 +29,9 @@ class FunnyOrDieIE(InfoExtractor):
'description': 'Please use this to sell something. www.jonlajoie.com', 'description': 'Please use this to sell something. www.jonlajoie.com',
'thumbnail': 're:^http:.*\.jpg$', 'thumbnail': 're:^http:.*\.jpg$',
}, },
}, {
'url': 'http://www.funnyordie.com/articles/ebf5e34fc8/10-hours-of-walking-in-nyc-as-a-man',
'only_matching': True,
}] }]
def _real_extract(self, url): def _real_extract(self, url):
@ -37,7 +40,7 @@ class FunnyOrDieIE(InfoExtractor):
video_id = mobj.group('id') video_id = mobj.group('id')
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
links = re.findall(r'<source src="([^"]+/v)\d+\.([^"]+)" type=\'video', webpage) links = re.findall(r'<source src="([^"]+/v)[^"]+\.([^"]+)" type=\'video', webpage)
if not links: if not links:
raise ExtractorError('No media links available for %s' % video_id) raise ExtractorError('No media links available for %s' % video_id)

View File

@ -28,6 +28,7 @@ from .brightcove import BrightcoveIE
from .ooyala import OoyalaIE from .ooyala import OoyalaIE
from .rutv import RUTVIE from .rutv import RUTVIE
from .smotri import SmotriIE from .smotri import SmotriIE
from .condenast import CondeNastIE
class GenericIE(InfoExtractor): class GenericIE(InfoExtractor):
@ -155,7 +156,6 @@ class GenericIE(InfoExtractor):
# funnyordie embed # funnyordie embed
{ {
'url': 'http://www.theguardian.com/world/2014/mar/11/obama-zach-galifianakis-between-two-ferns', 'url': 'http://www.theguardian.com/world/2014/mar/11/obama-zach-galifianakis-between-two-ferns',
'md5': '7cf780be104d40fea7bae52eed4a470e',
'info_dict': { 'info_dict': {
'id': '18e820ec3f', 'id': '18e820ec3f',
'ext': 'mp4', 'ext': 'mp4',
@ -180,13 +180,13 @@ class GenericIE(InfoExtractor):
# Embedded TED video # Embedded TED video
{ {
'url': 'http://en.support.wordpress.com/videos/ted-talks/', 'url': 'http://en.support.wordpress.com/videos/ted-talks/',
'md5': 'deeeabcc1085eb2ba205474e7235a3d5', 'md5': '65fdff94098e4a607385a60c5177c638',
'info_dict': { 'info_dict': {
'id': '981', 'id': '1969',
'ext': 'mp4', 'ext': 'mp4',
'title': 'My web playroom', 'title': 'Hidden miracles of the natural world',
'uploader': 'Ze Frank', 'uploader': 'Louie Schwartzberg',
'description': 'md5:ddb2a40ecd6b6a147e400e535874947b', 'description': 'md5:8145d19d320ff3e52f28401f4c4283b9',
} }
}, },
# Embeded Ustream video # Embeded Ustream video
@ -226,21 +226,6 @@ class GenericIE(InfoExtractor):
'skip_download': 'Requires rtmpdump' 'skip_download': 'Requires rtmpdump'
} }
}, },
# smotri embed
{
'url': 'http://rbctv.rbc.ru/archive/news/562949990879132.shtml',
'md5': 'ec40048448e9284c9a1de77bb188108b',
'info_dict': {
'id': 'v27008541fad',
'ext': 'mp4',
'title': 'Крым и Севастополь вошли в состав России',
'description': 'md5:fae01b61f68984c7bd2fa741e11c3175',
'duration': 900,
'upload_date': '20140318',
'uploader': 'rbctv_2012_4',
'uploader_id': 'rbctv_2012_4',
},
},
# Condé Nast embed # Condé Nast embed
{ {
'url': 'http://www.wired.com/2014/04/honda-asimo/', 'url': 'http://www.wired.com/2014/04/honda-asimo/',
@ -295,13 +280,13 @@ class GenericIE(InfoExtractor):
{ {
'url': 'https://play.google.com/store/apps/details?id=com.gameloft.android.ANMP.GloftA8HM', 'url': 'https://play.google.com/store/apps/details?id=com.gameloft.android.ANMP.GloftA8HM',
'info_dict': { 'info_dict': {
'id': 'jpSGZsgga_I', 'id': '4vAffPZIT44',
'ext': 'mp4', 'ext': 'mp4',
'title': 'Asphalt 8: Airborne - Launch Trailer', 'title': 'Asphalt 8: Airborne - Update - Welcome to Dubai!',
'uploader': 'Gameloft', 'uploader': 'Gameloft',
'uploader_id': 'gameloft', 'uploader_id': 'gameloft',
'upload_date': '20130821', 'upload_date': '20140828',
'description': 'md5:87bd95f13d8be3e7da87a5f2c443106a', 'description': 'md5:c80da9ed3d83ae6d1876c834de03e1c4',
}, },
'params': { 'params': {
'skip_download': True, 'skip_download': True,
@ -340,7 +325,7 @@ class GenericIE(InfoExtractor):
'ext': 'mp4', 'ext': 'mp4',
'age_limit': 18, 'age_limit': 18,
'uploader': 'www.handjobhub.com', 'uploader': 'www.handjobhub.com',
'title': 'Busty Blonde Siri Tit Fuck While Wank at Handjob Hub', 'title': 'Busty Blonde Siri Tit Fuck While Wank at HandjobHub.com',
} }
}, },
# RSS feed # RSS feed
@ -382,14 +367,59 @@ class GenericIE(InfoExtractor):
'thumbnail': 're:^https?://.*\.jpg$', 'thumbnail': 're:^https?://.*\.jpg$',
}, },
}, },
# Wistia embed
{
'url': 'http://education-portal.com/academy/lesson/north-american-exploration-failed-colonies-of-spain-france-england.html#lesson',
'md5': '8788b683c777a5cf25621eaf286d0c23',
'info_dict': {
'id': '1cfaf6b7ea',
'ext': 'mov',
'title': 'md5:51364a8d3d009997ba99656004b5e20d',
'duration': 643.0,
'filesize': 182808282,
'uploader': 'education-portal.com',
},
},
{
'url': 'http://thoughtworks.wistia.com/medias/uxjb0lwrcz',
'md5': 'baf49c2baa8a7de5f3fc145a8506dcd4',
'info_dict': {
'id': 'uxjb0lwrcz',
'ext': 'mp4',
'title': 'Conversation about Hexagonal Rails Part 1 - ThoughtWorks',
'duration': 1715.0,
'uploader': 'thoughtworks.wistia.com',
},
},
# Direct download with broken HEAD
{
'url': 'http://ai-radio.org:8000/radio.opus',
'info_dict': {
'id': 'radio',
'ext': 'opus',
'title': 'radio',
},
'params': {
'skip_download': True, # infinite live stream
},
'expected_warnings': [
r'501.*Not Implemented'
],
},
# Soundcloud embed
{
'url': 'http://nakedsecurity.sophos.com/2014/10/29/sscc-171-are-you-sure-that-1234-is-a-bad-password-podcast/',
'info_dict': {
'id': '174391317',
'ext': 'mp3',
'description': 'md5:ff867d6b555488ad3c52572bb33d432c',
'uploader': 'Sophos Security',
'title': 'Chet Chat 171 - Oct 29, 2014',
'upload_date': '20141029',
}
}
] ]
def report_download_webpage(self, video_id):
"""Report webpage download."""
if not self._downloader.params.get('test', False):
self._downloader.report_warning('Falling back on generic information extractor.')
super(GenericIE, self).report_download_webpage(video_id)
def report_following_redirect(self, new_url): def report_following_redirect(self, new_url):
"""Report information extraction.""" """Report information extraction."""
self._downloader.to_screen('[redirect] Following redirect to %s' % new_url) self._downloader.to_screen('[redirect] Following redirect to %s' % new_url)
@ -484,11 +514,13 @@ class GenericIE(InfoExtractor):
'Set --default-search "ytsearch" (or run youtube-dl "ytsearch:%s" ) to search YouTube' 'Set --default-search "ytsearch" (or run youtube-dl "ytsearch:%s" ) to search YouTube'
) % (url, url), expected=True) ) % (url, url), expected=True)
else: else:
assert ':' in default_search if ':' not in default_search:
default_search += ':'
return self.url_result(default_search + url) return self.url_result(default_search + url)
url, smuggled_data = unsmuggle_url(url) url, smuggled_data = unsmuggle_url(url)
force_videoid = None force_videoid = None
is_intentional = smuggled_data and smuggled_data.get('to_generic')
if smuggled_data and 'force_videoid' in smuggled_data: if smuggled_data and 'force_videoid' in smuggled_data:
force_videoid = smuggled_data['force_videoid'] force_videoid = smuggled_data['force_videoid']
video_id = force_videoid video_id = force_videoid
@ -498,14 +530,14 @@ class GenericIE(InfoExtractor):
self.to_screen('%s: Requesting header' % video_id) self.to_screen('%s: Requesting header' % video_id)
head_req = HEADRequest(url) head_req = HEADRequest(url)
response = self._request_webpage( head_response = self._request_webpage(
head_req, video_id, head_req, video_id,
note=False, errnote='Could not send HEAD request to %s' % url, note=False, errnote='Could not send HEAD request to %s' % url,
fatal=False) fatal=False)
if response is not False: if head_response is not False:
# Check for redirect # Check for redirect
new_url = response.geturl() new_url = head_response.geturl()
if url != new_url: if url != new_url:
self.report_following_redirect(new_url) self.report_following_redirect(new_url)
if force_videoid: if force_videoid:
@ -513,31 +545,35 @@ class GenericIE(InfoExtractor):
new_url, {'force_videoid': force_videoid}) new_url, {'force_videoid': force_videoid})
return self.url_result(new_url) return self.url_result(new_url)
# Check for direct link to a video full_response = None
content_type = response.headers.get('Content-Type', '') if head_response is False:
m = re.match(r'^(?P<type>audio|video|application(?=/ogg$))/(?P<format_id>.+)$', content_type) full_response = self._request_webpage(url, video_id)
if m: head_response = full_response
upload_date = response.headers.get('Last-Modified')
if upload_date:
upload_date = unified_strdate(upload_date)
return {
'id': video_id,
'title': os.path.splitext(url_basename(url))[0],
'formats': [{
'format_id': m.group('format_id'),
'url': url,
'vcodec': 'none' if m.group('type') == 'audio' else None
}],
'upload_date': upload_date,
}
try: # Check for direct link to a video
content_type = head_response.headers.get('Content-Type', '')
m = re.match(r'^(?P<type>audio|video|application(?=/ogg$))/(?P<format_id>.+)$', content_type)
if m:
upload_date = unified_strdate(
head_response.headers.get('Last-Modified'))
return {
'id': video_id,
'title': os.path.splitext(url_basename(url))[0],
'formats': [{
'format_id': m.group('format_id'),
'url': url,
'vcodec': 'none' if m.group('type') == 'audio' else None
}],
'upload_date': upload_date,
}
if not self._downloader.params.get('test', False) and not is_intentional:
self._downloader.report_warning('Falling back on generic information extractor.')
if full_response:
webpage = self._webpage_read_content(full_response, url, video_id)
else:
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
except ValueError:
# since this is the last-resort InfoExtractor, if
# this error is thrown, it'll be thrown here
raise ExtractorError('Failed to download URL: %s' % url)
self.report_extraction(video_id) self.report_extraction(video_id)
# Is it an RSS feed? # Is it an RSS feed?
@ -584,7 +620,9 @@ class GenericIE(InfoExtractor):
# Helper method # Helper method
def _playlist_from_matches(matches, getter, ie=None): def _playlist_from_matches(matches, getter, ie=None):
urlrs = orderedSet(self.url_result(getter(m), ie) for m in matches) urlrs = orderedSet(
self.url_result(self._proto_relative_url(getter(m)), ie)
for m in matches)
return self.playlist_result( return self.playlist_result(
urlrs, playlist_id=video_id, playlist_title=video_title) urlrs, playlist_id=video_id, playlist_title=video_title)
@ -611,13 +649,13 @@ class GenericIE(InfoExtractor):
if mobj: if mobj:
player_url = unescapeHTML(mobj.group('url')) player_url = unescapeHTML(mobj.group('url'))
surl = smuggle_url(player_url, {'Referer': url}) surl = smuggle_url(player_url, {'Referer': url})
return self.url_result(surl, 'Vimeo') return self.url_result(surl)
# Look for embedded (swf embed) Vimeo player # Look for embedded (swf embed) Vimeo player
mobj = re.search( mobj = re.search(
r'<embed[^>]+?src="(https?://(?:www\.)?vimeo\.com/moogaloop\.swf.+?)"', webpage) r'<embed[^>]+?src="((?:https?:)?//(?:www\.)?vimeo\.com/moogaloop\.swf.+?)"', webpage)
if mobj: if mobj:
return self.url_result(mobj.group(1), 'Vimeo') return self.url_result(mobj.group(1))
# Look for embedded YouTube player # Look for embedded YouTube player
matches = re.findall(r'''(?x) matches = re.findall(r'''(?x)
@ -625,15 +663,16 @@ class GenericIE(InfoExtractor):
<iframe[^>]+?src=| <iframe[^>]+?src=|
data-video-url=| data-video-url=|
<embed[^>]+?src=| <embed[^>]+?src=|
embedSWF\(?:\s* embedSWF\(?:\s*|
new\s+SWFObject\(
) )
(["\']) (["\'])
(?P<url>(?:https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/ (?P<url>(?:https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/
(?:embed|v)/.+?) (?:embed|v|p)/.+?)
\1''', webpage) \1''', webpage)
if matches: if matches:
return _playlist_from_matches( return _playlist_from_matches(
matches, lambda m: unescapeHTML(m[1]), ie='Youtube') matches, lambda m: unescapeHTML(m[1]))
# Look for embedded Dailymotion player # Look for embedded Dailymotion player
matches = re.findall( matches = re.findall(
@ -642,19 +681,42 @@ class GenericIE(InfoExtractor):
return _playlist_from_matches( return _playlist_from_matches(
matches, lambda m: unescapeHTML(m[1])) matches, lambda m: unescapeHTML(m[1]))
# Look for embedded Dailymotion playlist player (#3822)
m = re.search(
r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.[a-z]{2,3}/widget/jukebox\?.+?)\1', webpage)
if m:
playlists = re.findall(
r'list\[\]=/playlist/([^/]+)/', unescapeHTML(m.group('url')))
if playlists:
return _playlist_from_matches(
playlists, lambda p: '//dailymotion.com/playlist/%s' % p)
# Look for embedded Wistia player # Look for embedded Wistia player
match = re.search( match = re.search(
r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:fast\.)?wistia\.net/embed/iframe/.+?)\1', webpage) r'<(?:meta[^>]+?content|iframe[^>]+?src)=(["\'])(?P<url>(?:https?:)?//(?:fast\.)?wistia\.net/embed/iframe/.+?)\1', webpage)
if match: if match:
embed_url = self._proto_relative_url(
unescapeHTML(match.group('url')))
return { return {
'_type': 'url_transparent', '_type': 'url_transparent',
'url': unescapeHTML(match.group('url')), 'url': embed_url,
'ie_key': 'Wistia', 'ie_key': 'Wistia',
'uploader': video_uploader, 'uploader': video_uploader,
'title': video_title, 'title': video_title,
'id': video_id, 'id': video_id,
} }
match = re.search(r'(?:id=["\']wistia_|data-wistia-?id=["\']|Wistia\.embed\(["\'])(?P<id>[^"\']+)', webpage)
if match:
return {
'_type': 'url_transparent',
'url': 'http://fast.wistia.net/embed/iframe/{0:}'.format(match.group('id')),
'ie_key': 'Wistia',
'uploader': video_uploader,
'title': video_title,
'id': match.group('id')
}
# Look for embedded blip.tv player # Look for embedded blip.tv player
mobj = re.search(r'<meta\s[^>]*https?://api\.blip\.tv/\w+/redirect/\w+/(\d+)', webpage) mobj = re.search(r'<meta\s[^>]*https?://api\.blip\.tv/\w+/redirect/\w+/(\d+)', webpage)
if mobj: if mobj:
@ -788,7 +850,7 @@ class GenericIE(InfoExtractor):
# Look for embeded soundcloud player # Look for embeded soundcloud player
mobj = re.search( mobj = re.search(
r'<iframe src="(?P<url>https?://(?:w\.)?soundcloud\.com/player[^"]+)"', r'<iframe\s+(?:[a-zA-Z0-9_-]+="[^"]+"\s+)*src="(?P<url>https?://(?:w\.)?soundcloud\.com/player[^"]+)"',
webpage) webpage)
if mobj is not None: if mobj is not None:
url = unescapeHTML(mobj.group('url')) url = unescapeHTML(mobj.group('url'))
@ -825,59 +887,69 @@ class GenericIE(InfoExtractor):
return self.url_result(mobj.group('url'), 'SBS') return self.url_result(mobj.group('url'), 'SBS')
mobj = re.search( mobj = re.search(
r'<iframe[^>]+?src=(["\'])(?P<url>https?://m\.mlb\.com/shared/video/embed/embed\.html\?.+?)\1', r'<iframe[^>]+?src=(["\'])(?P<url>https?://m(?:lb)?\.mlb\.com/shared/video/embed/embed\.html\?.+?)\1',
webpage) webpage)
if mobj is not None: if mobj is not None:
return self.url_result(mobj.group('url'), 'MLB') return self.url_result(mobj.group('url'), 'MLB')
mobj = re.search(
r'<iframe[^>]+?src=(["\'])(?P<url>%s)\1' % CondeNastIE.EMBED_URL,
webpage)
if mobj is not None:
return self.url_result(self._proto_relative_url(mobj.group('url'), scheme='http:'), 'CondeNast')
def check_video(vurl):
vpath = compat_urlparse.urlparse(vurl).path
vext = determine_ext(vpath)
return '.' in vpath and vext not in ('swf', 'png', 'jpg', 'srt', 'sbv', 'sub', 'vtt', 'ttml')
def filter_video(urls):
return list(filter(check_video, urls))
# Start with something easy: JW Player in SWFObject # Start with something easy: JW Player in SWFObject
found = re.findall(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage) found = filter_video(re.findall(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage))
if not found: if not found:
# Look for gorilla-vid style embedding # Look for gorilla-vid style embedding
found = re.findall(r'''(?sx) found = filter_video(re.findall(r'''(?sx)
(?: (?:
jw_plugins| jw_plugins|
JWPlayerOptions| JWPlayerOptions|
jwplayer\s*\(\s*["'][^'"]+["']\s*\)\s*\.setup jwplayer\s*\(\s*["'][^'"]+["']\s*\)\s*\.setup
) )
.*?file\s*:\s*["\'](.*?)["\']''', webpage) .*?file\s*:\s*["\'](.*?)["\']''', webpage))
if not found: if not found:
# Broaden the search a little bit # Broaden the search a little bit
found = re.findall(r'[^A-Za-z0-9]?(?:file|source)=(http[^\'"&]*)', webpage) found = filter_video(re.findall(r'[^A-Za-z0-9]?(?:file|source)=(http[^\'"&]*)', webpage))
if not found: if not found:
# Broaden the findall a little bit: JWPlayer JS loader # Broaden the findall a little bit: JWPlayer JS loader
found = re.findall(r'[^A-Za-z0-9]?file["\']?:\s*["\'](http(?![^\'"]+\.[0-9]+[\'"])[^\'"]+)["\']', webpage) found = filter_video(re.findall(
r'[^A-Za-z0-9]?file["\']?:\s*["\'](http(?![^\'"]+\.[0-9]+[\'"])[^\'"]+)["\']', webpage))
if not found: if not found:
# Flow player # Flow player
found = re.findall(r'''(?xs) found = filter_video(re.findall(r'''(?xs)
flowplayer\("[^"]+",\s* flowplayer\("[^"]+",\s*
\{[^}]+?\}\s*, \{[^}]+?\}\s*,
\s*{[^}]+? ["']?clip["']?\s*:\s*\{\s* \s*{[^}]+? ["']?clip["']?\s*:\s*\{\s*
["']?url["']?\s*:\s*["']([^"']+)["'] ["']?url["']?\s*:\s*["']([^"']+)["']
''', webpage) ''', webpage))
if not found: if not found:
# Try to find twitter cards info # Try to find twitter cards info
found = re.findall(r'<meta (?:property|name)="twitter:player:stream" (?:content|value)="(.+?)"', webpage) found = filter_video(re.findall(
r'<meta (?:property|name)="twitter:player:stream" (?:content|value)="(.+?)"', webpage))
if not found: if not found:
# We look for Open Graph info: # We look for Open Graph info:
# We have to match any number spaces between elements, some sites try to align them (eg.: statigr.am) # We have to match any number spaces between elements, some sites try to align them (eg.: statigr.am)
m_video_type = re.findall(r'<meta.*?property="og:video:type".*?content="video/(.*?)"', webpage) m_video_type = re.findall(r'<meta.*?property="og:video:type".*?content="video/(.*?)"', webpage)
# We only look in og:video if the MIME type is a video, don't try if it's a Flash player: # We only look in og:video if the MIME type is a video, don't try if it's a Flash player:
if m_video_type is not None: if m_video_type is not None:
def check_video(vurl): found = filter_video(re.findall(r'<meta.*?property="og:video".*?content="(.*?)"', webpage))
vpath = compat_urlparse.urlparse(vurl).path
vext = determine_ext(vpath)
return '.' in vpath and vext not in ('swf', 'png', 'jpg')
found = list(filter(
check_video,
re.findall(r'<meta.*?property="og:video".*?content="(.*?)"', webpage)))
if not found: if not found:
# HTML5 video # HTML5 video
found = re.findall(r'(?s)<video[^<]*(?:>.*?<source[^>]+)? src="([^"]+)"', webpage) found = re.findall(r'(?s)<video[^<]*(?:>.*?<source[^>]*)?\s+src="([^"]+)"', webpage)
if not found: if not found:
found = re.search( found = re.search(
r'(?i)<meta\s+(?=(?:[a-z-]+="[^"]+"\s+)*http-equiv="refresh")' r'(?i)<meta\s+(?=(?:[a-z-]+="[^"]+"\s+)*http-equiv="refresh")'
r'(?:[a-z-]+="[^"]+"\s+)*?content="[0-9]{,2};url=\'([^\']+)\'"', r'(?:[a-z-]+="[^"]+"\s+)*?content="[0-9]{,2};url=\'?([^\'"]+)',
webpage) webpage)
if found: if found:
new_url = found.group(1) new_url = found.group(1)

View File

@ -0,0 +1,40 @@
# coding: utf-8
from __future__ import unicode_literals
from .common import InfoExtractor
class GlideIE(InfoExtractor):
IE_DESC = 'Glide mobile video messages (glide.me)'
_VALID_URL = r'https?://share\.glide\.me/(?P<id>[A-Za-z0-9\-=_+]+)'
_TEST = {
'url': 'http://share.glide.me/UZF8zlmuQbe4mr+7dCiQ0w==',
'md5': '4466372687352851af2d131cfaa8a4c7',
'info_dict': {
'id': 'UZF8zlmuQbe4mr+7dCiQ0w==',
'ext': 'mp4',
'title': 'Damon Timm\'s Glide message',
'thumbnail': 're:^https?://.*?\.cloudfront\.net/.*\.jpg$',
}
}
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>(.*?)</title>', webpage, 'title')
video_url = self.http_scheme() + self._search_regex(
r'<source src="(.*?)" type="video/mp4">', webpage, 'video URL')
thumbnail_url = self._search_regex(
r'<img id="video-thumbnail" src="(.*?)"',
webpage, 'thumbnail url', fatal=False)
thumbnail = (
thumbnail_url if thumbnail_url is None
else self.http_scheme() + thumbnail_url)
return {
'id': video_id,
'title': title,
'url': video_url,
'thumbnail': thumbnail,
}

View File

@ -0,0 +1,398 @@
# coding: utf-8
from __future__ import unicode_literals
import random
import math
from .common import InfoExtractor
from ..utils import (
ExtractorError,
float_or_none,
compat_str,
compat_chr,
compat_ord,
)
class GloboIE(InfoExtractor):
_VALID_URL = 'https?://.+?\.globo\.com/(?P<id>.+)'
_API_URL_TEMPLATE = 'http://api.globovideos.com/videos/%s/playlist'
_SECURITY_URL_TEMPLATE = 'http://security.video.globo.com/videos/%s/hash?player=flash&version=2.9.9.50&resource_id=%s'
_VIDEOID_REGEXES = [
r'\bdata-video-id="(\d+)"',
r'\bdata-player-videosids="(\d+)"',
r'<div[^>]+\bid="(\d+)"',
]
_RESIGN_EXPIRATION = 86400
_TESTS = [
{
'url': 'http://globotv.globo.com/sportv/futebol-nacional/v/os-gols-de-atletico-mg-3-x-2-santos-pela-24a-rodada-do-brasileirao/3654973/',
'md5': '03ebf41cb7ade43581608b7d9b71fab0',
'info_dict': {
'id': '3654973',
'ext': 'mp4',
'title': 'Os gols de Atlético-MG 3 x 2 Santos pela 24ª rodada do Brasileirão',
'duration': 251.585,
'uploader': 'SporTV',
'uploader_id': 698,
'like_count': int,
}
},
{
'url': 'http://g1.globo.com/carros/autoesporte/videos/t/exclusivos-do-g1/v/mercedes-benz-gla-passa-por-teste-de-colisao-na-europa/3607726/',
'md5': 'b3ccc801f75cd04a914d51dadb83a78d',
'info_dict': {
'id': '3607726',
'ext': 'mp4',
'title': 'Mercedes-Benz GLA passa por teste de colisão na Europa',
'duration': 103.204,
'uploader': 'Globo.com',
'uploader_id': 265,
'like_count': int,
}
},
{
'url': 'http://g1.globo.com/jornal-nacional/noticia/2014/09/novidade-na-fiscalizacao-de-bagagem-pela-receita-provoca-discussoes.html',
'md5': '307fdeae4390ccfe6ba1aa198cf6e72b',
'info_dict': {
'id': '3652183',
'ext': 'mp4',
'title': 'Receita Federal explica como vai fiscalizar bagagens de quem retorna ao Brasil de avião',
'duration': 110.711,
'uploader': 'Rede Globo',
'uploader_id': 196,
'like_count': int,
}
},
]
class MD5():
HEX_FORMAT_LOWERCASE = 0
HEX_FORMAT_UPPERCASE = 1
BASE64_PAD_CHARACTER_DEFAULT_COMPLIANCE = ''
BASE64_PAD_CHARACTER_RFC_COMPLIANCE = '='
PADDING = '=0xFF01DD'
hexcase = 0
b64pad = ''
def __init__(self):
pass
class JSArray(list):
def __getitem__(self, y):
try:
return list.__getitem__(self, y)
except IndexError:
return 0
def __setitem__(self, i, y):
try:
return list.__setitem__(self, i, y)
except IndexError:
self.extend([0] * (i - len(self) + 1))
self[-1] = y
@classmethod
def hex_md5(cls, param1):
return cls.rstr2hex(cls.rstr_md5(cls.str2rstr_utf8(param1)))
@classmethod
def b64_md5(cls, param1, param2=None):
return cls.rstr2b64(cls.rstr_md5(cls.str2rstr_utf8(param1, param2)))
@classmethod
def any_md5(cls, param1, param2):
return cls.rstr2any(cls.rstr_md5(cls.str2rstr_utf8(param1)), param2)
@classmethod
def rstr_md5(cls, param1):
return cls.binl2rstr(cls.binl_md5(cls.rstr2binl(param1), len(param1) * 8))
@classmethod
def rstr2hex(cls, param1):
_loc_2 = '0123456789ABCDEF' if cls.hexcase else '0123456789abcdef'
_loc_3 = ''
for _loc_5 in range(0, len(param1)):
_loc_4 = compat_ord(param1[_loc_5])
_loc_3 += _loc_2[_loc_4 >> 4 & 15] + _loc_2[_loc_4 & 15]
return _loc_3
@classmethod
def rstr2b64(cls, param1):
_loc_2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
_loc_3 = ''
_loc_4 = len(param1)
for _loc_5 in range(0, _loc_4, 3):
_loc_6_1 = compat_ord(param1[_loc_5]) << 16
_loc_6_2 = compat_ord(param1[_loc_5 + 1]) << 8 if _loc_5 + 1 < _loc_4 else 0
_loc_6_3 = compat_ord(param1[_loc_5 + 2]) if _loc_5 + 2 < _loc_4 else 0
_loc_6 = _loc_6_1 | _loc_6_2 | _loc_6_3
for _loc_7 in range(0, 4):
if _loc_5 * 8 + _loc_7 * 6 > len(param1) * 8:
_loc_3 += cls.b64pad
else:
_loc_3 += _loc_2[_loc_6 >> 6 * (3 - _loc_7) & 63]
return _loc_3
@staticmethod
def rstr2any(param1, param2):
_loc_3 = len(param2)
_loc_4 = []
_loc_9 = [0] * ((len(param1) >> 2) + 1)
for _loc_5 in range(0, len(_loc_9)):
_loc_9[_loc_5] = compat_ord(param1[_loc_5 * 2]) << 8 | compat_ord(param1[_loc_5 * 2 + 1])
while len(_loc_9) > 0:
_loc_8 = []
_loc_7 = 0
for _loc_5 in range(0, len(_loc_9)):
_loc_7 = (_loc_7 << 16) + _loc_9[_loc_5]
_loc_6 = math.floor(_loc_7 / _loc_3)
_loc_7 -= _loc_6 * _loc_3
if len(_loc_8) > 0 or _loc_6 > 0:
_loc_8[len(_loc_8)] = _loc_6
_loc_4[len(_loc_4)] = _loc_7
_loc_9 = _loc_8
_loc_10 = ''
_loc_5 = len(_loc_4) - 1
while _loc_5 >= 0:
_loc_10 += param2[_loc_4[_loc_5]]
_loc_5 -= 1
return _loc_10
@classmethod
def str2rstr_utf8(cls, param1, param2=None):
_loc_3 = ''
_loc_4 = -1
if not param2:
param2 = cls.PADDING
param1 = param1 + param2[1:9]
while True:
_loc_4 += 1
if _loc_4 >= len(param1):
break
_loc_5 = compat_ord(param1[_loc_4])
_loc_6 = compat_ord(param1[_loc_4 + 1]) if _loc_4 + 1 < len(param1) else 0
if 55296 <= _loc_5 <= 56319 and 56320 <= _loc_6 <= 57343:
_loc_5 = 65536 + ((_loc_5 & 1023) << 10) + (_loc_6 & 1023)
_loc_4 += 1
if _loc_5 <= 127:
_loc_3 += compat_chr(_loc_5)
continue
if _loc_5 <= 2047:
_loc_3 += compat_chr(192 | _loc_5 >> 6 & 31) + compat_chr(128 | _loc_5 & 63)
continue
if _loc_5 <= 65535:
_loc_3 += compat_chr(224 | _loc_5 >> 12 & 15) + compat_chr(128 | _loc_5 >> 6 & 63) + compat_chr(
128 | _loc_5 & 63)
continue
if _loc_5 <= 2097151:
_loc_3 += compat_chr(240 | _loc_5 >> 18 & 7) + compat_chr(128 | _loc_5 >> 12 & 63) + compat_chr(
128 | _loc_5 >> 6 & 63) + compat_chr(128 | _loc_5 & 63)
return _loc_3
@staticmethod
def rstr2binl(param1):
_loc_2 = [0] * ((len(param1) >> 2) + 1)
for _loc_3 in range(0, len(_loc_2)):
_loc_2[_loc_3] = 0
for _loc_3 in range(0, len(param1) * 8, 8):
_loc_2[_loc_3 >> 5] |= (compat_ord(param1[_loc_3 // 8]) & 255) << _loc_3 % 32
return _loc_2
@staticmethod
def binl2rstr(param1):
_loc_2 = ''
for _loc_3 in range(0, len(param1) * 32, 8):
_loc_2 += compat_chr(param1[_loc_3 >> 5] >> _loc_3 % 32 & 255)
return _loc_2
@classmethod
def binl_md5(cls, param1, param2):
param1 = cls.JSArray(param1)
param1[param2 >> 5] |= 128 << param2 % 32
param1[(param2 + 64 >> 9 << 4) + 14] = param2
_loc_3 = 1732584193
_loc_4 = -271733879
_loc_5 = -1732584194
_loc_6 = 271733878
for _loc_7 in range(0, len(param1), 16):
_loc_8 = _loc_3
_loc_9 = _loc_4
_loc_10 = _loc_5
_loc_11 = _loc_6
_loc_3 = cls.md5_ff(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 0], 7, -680876936)
_loc_6 = cls.md5_ff(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 1], 12, -389564586)
_loc_5 = cls.md5_ff(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 2], 17, 606105819)
_loc_4 = cls.md5_ff(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 3], 22, -1044525330)
_loc_3 = cls.md5_ff(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 4], 7, -176418897)
_loc_6 = cls.md5_ff(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 5], 12, 1200080426)
_loc_5 = cls.md5_ff(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 6], 17, -1473231341)
_loc_4 = cls.md5_ff(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 7], 22, -45705983)
_loc_3 = cls.md5_ff(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 8], 7, 1770035416)
_loc_6 = cls.md5_ff(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 9], 12, -1958414417)
_loc_5 = cls.md5_ff(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 10], 17, -42063)
_loc_4 = cls.md5_ff(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 11], 22, -1990404162)
_loc_3 = cls.md5_ff(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 12], 7, 1804603682)
_loc_6 = cls.md5_ff(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 13], 12, -40341101)
_loc_5 = cls.md5_ff(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 14], 17, -1502002290)
_loc_4 = cls.md5_ff(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 15], 22, 1236535329)
_loc_3 = cls.md5_gg(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 1], 5, -165796510)
_loc_6 = cls.md5_gg(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 6], 9, -1069501632)
_loc_5 = cls.md5_gg(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 11], 14, 643717713)
_loc_4 = cls.md5_gg(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 0], 20, -373897302)
_loc_3 = cls.md5_gg(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 5], 5, -701558691)
_loc_6 = cls.md5_gg(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 10], 9, 38016083)
_loc_5 = cls.md5_gg(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 15], 14, -660478335)
_loc_4 = cls.md5_gg(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 4], 20, -405537848)
_loc_3 = cls.md5_gg(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 9], 5, 568446438)
_loc_6 = cls.md5_gg(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 14], 9, -1019803690)
_loc_5 = cls.md5_gg(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 3], 14, -187363961)
_loc_4 = cls.md5_gg(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 8], 20, 1163531501)
_loc_3 = cls.md5_gg(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 13], 5, -1444681467)
_loc_6 = cls.md5_gg(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 2], 9, -51403784)
_loc_5 = cls.md5_gg(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 7], 14, 1735328473)
_loc_4 = cls.md5_gg(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 12], 20, -1926607734)
_loc_3 = cls.md5_hh(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 5], 4, -378558)
_loc_6 = cls.md5_hh(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 8], 11, -2022574463)
_loc_5 = cls.md5_hh(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 11], 16, 1839030562)
_loc_4 = cls.md5_hh(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 14], 23, -35309556)
_loc_3 = cls.md5_hh(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 1], 4, -1530992060)
_loc_6 = cls.md5_hh(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 4], 11, 1272893353)
_loc_5 = cls.md5_hh(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 7], 16, -155497632)
_loc_4 = cls.md5_hh(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 10], 23, -1094730640)
_loc_3 = cls.md5_hh(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 13], 4, 681279174)
_loc_6 = cls.md5_hh(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 0], 11, -358537222)
_loc_5 = cls.md5_hh(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 3], 16, -722521979)
_loc_4 = cls.md5_hh(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 6], 23, 76029189)
_loc_3 = cls.md5_hh(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 9], 4, -640364487)
_loc_6 = cls.md5_hh(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 12], 11, -421815835)
_loc_5 = cls.md5_hh(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 15], 16, 530742520)
_loc_4 = cls.md5_hh(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 2], 23, -995338651)
_loc_3 = cls.md5_ii(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 0], 6, -198630844)
_loc_6 = cls.md5_ii(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 7], 10, 1126891415)
_loc_5 = cls.md5_ii(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 14], 15, -1416354905)
_loc_4 = cls.md5_ii(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 5], 21, -57434055)
_loc_3 = cls.md5_ii(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 12], 6, 1700485571)
_loc_6 = cls.md5_ii(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 3], 10, -1894986606)
_loc_5 = cls.md5_ii(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 10], 15, -1051523)
_loc_4 = cls.md5_ii(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 1], 21, -2054922799)
_loc_3 = cls.md5_ii(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 8], 6, 1873313359)
_loc_6 = cls.md5_ii(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 15], 10, -30611744)
_loc_5 = cls.md5_ii(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 6], 15, -1560198380)
_loc_4 = cls.md5_ii(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 13], 21, 1309151649)
_loc_3 = cls.md5_ii(_loc_3, _loc_4, _loc_5, _loc_6, param1[_loc_7 + 4], 6, -145523070)
_loc_6 = cls.md5_ii(_loc_6, _loc_3, _loc_4, _loc_5, param1[_loc_7 + 11], 10, -1120210379)
_loc_5 = cls.md5_ii(_loc_5, _loc_6, _loc_3, _loc_4, param1[_loc_7 + 2], 15, 718787259)
_loc_4 = cls.md5_ii(_loc_4, _loc_5, _loc_6, _loc_3, param1[_loc_7 + 9], 21, -343485551)
_loc_3 = cls.safe_add(_loc_3, _loc_8)
_loc_4 = cls.safe_add(_loc_4, _loc_9)
_loc_5 = cls.safe_add(_loc_5, _loc_10)
_loc_6 = cls.safe_add(_loc_6, _loc_11)
return [_loc_3, _loc_4, _loc_5, _loc_6]
@classmethod
def md5_cmn(cls, param1, param2, param3, param4, param5, param6):
return cls.safe_add(
cls.bit_rol(cls.safe_add(cls.safe_add(param2, param1), cls.safe_add(param4, param6)), param5), param3)
@classmethod
def md5_ff(cls, param1, param2, param3, param4, param5, param6, param7):
return cls.md5_cmn(param2 & param3 | ~param2 & param4, param1, param2, param5, param6, param7)
@classmethod
def md5_gg(cls, param1, param2, param3, param4, param5, param6, param7):
return cls.md5_cmn(param2 & param4 | param3 & ~param4, param1, param2, param5, param6, param7)
@classmethod
def md5_hh(cls, param1, param2, param3, param4, param5, param6, param7):
return cls.md5_cmn(param2 ^ param3 ^ param4, param1, param2, param5, param6, param7)
@classmethod
def md5_ii(cls, param1, param2, param3, param4, param5, param6, param7):
return cls.md5_cmn(param3 ^ (param2 | ~param4), param1, param2, param5, param6, param7)
@classmethod
def safe_add(cls, param1, param2):
_loc_3 = (param1 & 65535) + (param2 & 65535)
_loc_4 = (param1 >> 16) + (param2 >> 16) + (_loc_3 >> 16)
return cls.lshift(_loc_4, 16) | _loc_3 & 65535
@classmethod
def bit_rol(cls, param1, param2):
return cls.lshift(param1, param2) | (param1 & 0xFFFFFFFF) >> (32 - param2)
@staticmethod
def lshift(value, count):
r = (0xFFFFFFFF & value) << count
return -(~(r - 1) & 0xFFFFFFFF) if r > 0x7FFFFFFF else r
def _real_extract(self, url):
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
video_id = self._search_regex(self._VIDEOID_REGEXES, webpage, 'video id')
video = self._download_json(
self._API_URL_TEMPLATE % video_id, video_id)['videos'][0]
title = video['title']
duration = float_or_none(video['duration'], 1000)
like_count = video['likes']
uploader = video['channel']
uploader_id = video['channel_id']
formats = []
for resource in video['resources']:
resource_id = resource.get('_id')
if not resource_id:
continue
security = self._download_json(
self._SECURITY_URL_TEMPLATE % (video_id, resource_id),
video_id, 'Downloading security hash for %s' % resource_id)
security_hash = security.get('hash')
if not security_hash:
message = security.get('message')
if message:
raise ExtractorError(
'%s returned error: %s' % (self.IE_NAME, message), expected=True)
continue
hash_code = security_hash[:2]
received_time = int(security_hash[2:12])
received_random = security_hash[12:22]
received_md5 = security_hash[22:]
sign_time = received_time + self._RESIGN_EXPIRATION
padding = '%010d' % random.randint(1, 10000000000)
signed_md5 = self.MD5.b64_md5(received_md5 + compat_str(sign_time) + padding)
signed_hash = hash_code + compat_str(received_time) + received_random + compat_str(sign_time) + padding + signed_md5
formats.append({
'url': '%s?h=%s&k=%s' % (resource['url'], signed_hash, 'flash'),
'format_id': resource_id,
'height': resource['height']
})
self._sort_formats(formats)
return {
'id': video_id,
'title': title,
'duration': duration,
'uploader': uploader,
'uploader_id': uploader_id,
'like_count': like_count,
'formats': formats
}

View File

@ -36,16 +36,16 @@ class GodTubeIE(InfoExtractor):
'http://www.godtube.com/resource/mediaplayer/%s.xml' % video_id.lower(), 'http://www.godtube.com/resource/mediaplayer/%s.xml' % video_id.lower(),
video_id, 'Downloading player config XML') video_id, 'Downloading player config XML')
video_url = config.find('.//file').text video_url = config.find('file').text
uploader = config.find('.//author').text uploader = config.find('author').text
timestamp = parse_iso8601(config.find('.//date').text) timestamp = parse_iso8601(config.find('date').text)
duration = parse_duration(config.find('.//duration').text) duration = parse_duration(config.find('duration').text)
thumbnail = config.find('.//image').text thumbnail = config.find('image').text
media = self._download_xml( media = self._download_xml(
'http://www.godtube.com/media/xml/?v=%s' % video_id, video_id, 'Downloading media XML') 'http://www.godtube.com/media/xml/?v=%s' % video_id, video_id, 'Downloading media XML')
title = media.find('.//title').text title = media.find('title').text
return { return {
'id': video_id, 'id': video_id,

View File

@ -0,0 +1,69 @@
# coding: utf-8
from __future__ import unicode_literals
from .common import InfoExtractor
from ..utils import (
compat_urlparse,
determine_ext,
)
class GolemIE(InfoExtractor):
_VALID_URL = r'^https?://video\.golem\.de/.+?/(?P<id>.+?)/'
_TEST = {
'url': 'http://video.golem.de/handy/14095/iphone-6-und-6-plus-test.html',
'md5': 'c1a2c0a3c863319651c7c992c5ee29bf',
'info_dict': {
'id': '14095',
'format_id': 'high',
'ext': 'mp4',
'title': 'iPhone 6 und 6 Plus - Test',
'duration': 300.44,
'filesize': 65309548,
}
}
_PREFIX = 'http://video.golem.de'
def _real_extract(self, url):
video_id = self._match_id(url)
config = self._download_xml(
'https://video.golem.de/xml/{0}.xml'.format(video_id), video_id)
info = {
'id': video_id,
'title': config.findtext('./title', 'golem'),
'duration': self._float(config.findtext('./playtime'), 'duration'),
}
formats = []
for e in config:
url = e.findtext('./url')
if not url:
continue
formats.append({
'format_id': e.tag,
'url': compat_urlparse.urljoin(self._PREFIX, url),
'height': self._int(e.get('height'), 'height'),
'width': self._int(e.get('width'), 'width'),
'filesize': self._int(e.findtext('filesize'), 'filesize'),
'ext': determine_ext(e.findtext('./filename')),
})
self._sort_formats(formats)
info['formats'] = formats
thumbnails = []
for e in config.findall('.//teaser'):
url = e.findtext('./url')
if not url:
continue
thumbnails.append({
'url': compat_urlparse.urljoin(self._PREFIX, url),
'width': self._int(e.get('width'), 'thumbnail width'),
'height': self._int(e.get('height'), 'thumbnail height'),
})
info['thumbnails'] = thumbnails
return info

View File

@ -1,13 +1,11 @@
# coding: utf-8 # coding: utf-8
from __future__ import unicode_literals from __future__ import unicode_literals
import datetime
import re import re
import codecs
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import ( from ..utils import unified_strdate
ExtractorError,
)
class GooglePlusIE(InfoExtractor): class GooglePlusIE(InfoExtractor):
@ -19,74 +17,57 @@ class GooglePlusIE(InfoExtractor):
'info_dict': { 'info_dict': {
'id': 'ZButuJc6CtH', 'id': 'ZButuJc6CtH',
'ext': 'flv', 'ext': 'flv',
'title': '嘆きの天使 降臨',
'upload_date': '20120613', 'upload_date': '20120613',
'uploader': '井上ヨシマサ', 'uploader': '井上ヨシマサ',
'title': '嘆きの天使 降臨',
} }
} }
def _real_extract(self, url): def _real_extract(self, url):
# Extract id from URL video_id = self._match_id(url)
mobj = re.match(self._VALID_URL, url)
video_id = mobj.group('id')
# Step 1, Retrieve post webpage to extract further information # Step 1, Retrieve post webpage to extract further information
webpage = self._download_webpage(url, video_id, 'Downloading entry webpage') webpage = self._download_webpage(url, video_id, 'Downloading entry webpage')
self.report_extraction(video_id) title = self._og_search_description(webpage).splitlines()[0]
upload_date = unified_strdate(self._html_search_regex(
# Extract update date
upload_date = self._html_search_regex(
r'''(?x)<a.+?class="o-U-s\s[^"]+"\s+style="display:\s*none"\s*> r'''(?x)<a.+?class="o-U-s\s[^"]+"\s+style="display:\s*none"\s*>
([0-9]{4}-[0-9]{2}-[0-9]{2})</a>''', ([0-9]{4}-[0-9]{2}-[0-9]{2})</a>''',
webpage, 'upload date', fatal=False, flags=re.VERBOSE) webpage, 'upload date', fatal=False, flags=re.VERBOSE))
if upload_date: uploader = self._html_search_regex(
# Convert timestring to a format suitable for filename r'rel="author".*?>(.*?)</a>', webpage, 'uploader', fatal=False)
upload_date = datetime.datetime.strptime(upload_date, "%Y-%m-%d")
upload_date = upload_date.strftime('%Y%m%d')
# Extract uploader
uploader = self._html_search_regex(r'rel\="author".*?>(.*?)</a>',
webpage, 'uploader', fatal=False)
# Extract title
# Get the first line for title
video_title = self._og_search_description(webpage).splitlines()[0]
# Step 2, Simulate clicking the image box to launch video # Step 2, Simulate clicking the image box to launch video
DOMAIN = 'https://plus.google.com/' DOMAIN = 'https://plus.google.com/'
video_page = self._search_regex(r'<a href="((?:%s)?photos/.*?)"' % re.escape(DOMAIN), video_page = self._search_regex(
r'<a href="((?:%s)?photos/.*?)"' % re.escape(DOMAIN),
webpage, 'video page URL') webpage, 'video page URL')
if not video_page.startswith(DOMAIN): if not video_page.startswith(DOMAIN):
video_page = DOMAIN + video_page video_page = DOMAIN + video_page
webpage = self._download_webpage(video_page, video_id, 'Downloading video page') webpage = self._download_webpage(video_page, video_id, 'Downloading video page')
def unicode_escape(s):
decoder = codecs.getdecoder('unicode_escape')
return re.sub(
r'\\u[0-9a-fA-F]{4,}',
lambda m: decoder(m.group(0))[0],
s)
# Extract video links all sizes # Extract video links all sizes
pattern = r'\d+,\d+,(\d+),"(http\://redirector\.googlevideo\.com.*?)"' formats = [{
mobj = re.findall(pattern, webpage) 'url': unicode_escape(video_url),
if len(mobj) == 0: 'ext': 'flv',
raise ExtractorError('Unable to extract video links') 'width': int(width),
'height': int(height),
# Sort in resolution } for width, height, video_url in re.findall(
links = sorted(mobj) r'\d+,(\d+),(\d+),"(https?://redirector\.googlevideo\.com.*?)"', webpage)]
self._sort_formats(formats)
# Choose the lowest of the sort, i.e. highest resolution
video_url = links[-1]
# Only get the url. The resolution part in the tuple has no use anymore
video_url = video_url[-1]
# Treat escaped \u0026 style hex
try:
video_url = video_url.decode("unicode_escape")
except AttributeError: # Python 3
video_url = bytes(video_url, 'ascii').decode('unicode-escape')
return { return {
'id': video_id, 'id': video_id,
'url': video_url, 'title': title,
'uploader': uploader, 'uploader': uploader,
'upload_date': upload_date, 'upload_date': upload_date,
'title': video_title, 'formats': formats,
'ext': 'flv',
} }

View File

@ -5,6 +5,7 @@ import re
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import ( from ..utils import (
ExtractorError,
determine_ext, determine_ext,
compat_urllib_parse, compat_urllib_parse,
compat_urllib_request, compat_urllib_request,
@ -12,20 +13,22 @@ from ..utils import (
class GorillaVidIE(InfoExtractor): class GorillaVidIE(InfoExtractor):
IE_DESC = 'GorillaVid.in and daclips.in' IE_DESC = 'GorillaVid.in, daclips.in and movpod.in'
_VALID_URL = r'''(?x) _VALID_URL = r'''(?x)
https?://(?P<host>(?:www\.)? https?://(?P<host>(?:www\.)?
(?:daclips\.in|gorillavid\.in))/ (?:daclips\.in|gorillavid\.in|movpod\.in))/
(?:embed-)?(?P<id>[0-9a-zA-Z]+)(?:-[0-9]+x[0-9]+\.html)? (?:embed-)?(?P<id>[0-9a-zA-Z]+)(?:-[0-9]+x[0-9]+\.html)?
''' '''
_FILE_NOT_FOUND_REGEX = r'>(?:404 - )?File Not Found<'
_TESTS = [{ _TESTS = [{
'url': 'http://gorillavid.in/06y9juieqpmi', 'url': 'http://gorillavid.in/06y9juieqpmi',
'md5': '5ae4a3580620380619678ee4875893ba', 'md5': '5ae4a3580620380619678ee4875893ba',
'info_dict': { 'info_dict': {
'id': '06y9juieqpmi', 'id': '06y9juieqpmi',
'ext': 'flv', 'ext': 'flv',
'title': 'Rebecca Black My Moment Official Music Video Reaction', 'title': 'Rebecca Black My Moment Official Music Video Reaction-6GK87Rc8bzQ',
'thumbnail': 're:http://.*\.jpg', 'thumbnail': 're:http://.*\.jpg',
}, },
}, { }, {
@ -43,9 +46,12 @@ class GorillaVidIE(InfoExtractor):
'info_dict': { 'info_dict': {
'id': '3rso4kdn6f9m', 'id': '3rso4kdn6f9m',
'ext': 'mp4', 'ext': 'mp4',
'title': 'Micro Pig piglets ready on 16th July 2009', 'title': 'Micro Pig piglets ready on 16th July 2009-bG0PdrCdxUc',
'thumbnail': 're:http://.*\.jpg', 'thumbnail': 're:http://.*\.jpg',
}, }
}, {
'url': 'http://movpod.in/0wguyyxi1yca',
'only_matching': True,
}] }]
def _real_extract(self, url): def _real_extract(self, url):
@ -54,6 +60,9 @@ class GorillaVidIE(InfoExtractor):
webpage = self._download_webpage('http://%s/%s' % (mobj.group('host'), video_id), video_id) webpage = self._download_webpage('http://%s/%s' % (mobj.group('host'), video_id), video_id)
if re.search(self._FILE_NOT_FOUND_REGEX, webpage) is not None:
raise ExtractorError('Video %s does not exist' % video_id, expected=True)
fields = dict(re.findall(r'''(?x)<input\s+ fields = dict(re.findall(r'''(?x)<input\s+
type="hidden"\s+ type="hidden"\s+
name="([^"]+)"\s+ name="([^"]+)"\s+
@ -69,14 +78,14 @@ class GorillaVidIE(InfoExtractor):
webpage = self._download_webpage(req, video_id, 'Downloading video page') webpage = self._download_webpage(req, video_id, 'Downloading video page')
title = self._search_regex(r'style="z-index: [0-9]+;">([0-9a-zA-Z ]+)(?:-.+)?</span>', webpage, 'title') title = self._search_regex(r'style="z-index: [0-9]+;">([^<]+)</span>', webpage, 'title')
thumbnail = self._search_regex(r'image:\'(http[^\']+)\',', webpage, 'thumbnail') video_url = self._search_regex(r'file\s*:\s*\'(http[^\']+)\',', webpage, 'file url')
url = self._search_regex(r'file: \'(http[^\']+)\',', webpage, 'file url') thumbnail = self._search_regex(r'image\s*:\s*\'(http[^\']+)\',', webpage, 'thumbnail', fatal=False)
formats = [{ formats = [{
'format_id': 'sd', 'format_id': 'sd',
'url': url, 'url': video_url,
'ext': determine_ext(url), 'ext': determine_ext(video_url),
'quality': 1, 'quality': 1,
}] }]

View File

@ -1,37 +1,33 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import re
import json
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import determine_ext
class HarkIE(InfoExtractor): class HarkIE(InfoExtractor):
_VALID_URL = r'https?://www\.hark\.com/clips/(.+?)-.+' _VALID_URL = r'https?://www\.hark\.com/clips/(?P<id>.+?)-.+'
_TEST = { _TEST = {
u'url': u'http://www.hark.com/clips/mmbzyhkgny-obama-beyond-the-afghan-theater-we-only-target-al-qaeda-on-may-23-2013', 'url': 'http://www.hark.com/clips/mmbzyhkgny-obama-beyond-the-afghan-theater-we-only-target-al-qaeda-on-may-23-2013',
u'file': u'mmbzyhkgny.mp3', 'md5': '6783a58491b47b92c7c1af5a77d4cbee',
u'md5': u'6783a58491b47b92c7c1af5a77d4cbee', 'info_dict': {
u'info_dict': { 'id': 'mmbzyhkgny',
u'title': u"Obama: 'Beyond The Afghan Theater, We Only Target Al Qaeda' on May 23, 2013", 'ext': 'mp3',
u'description': u'President Barack Obama addressed the nation live on May 23, 2013 in a speech aimed at addressing counter-terrorism policies including the use of drone strikes, detainees at Guantanamo Bay prison facility, and American citizens who are terrorists.', 'title': 'Obama: \'Beyond The Afghan Theater, We Only Target Al Qaeda\' on May 23, 2013',
u'duration': 11, 'description': 'President Barack Obama addressed the nation live on May 23, 2013 in a speech aimed at addressing counter-terrorism policies including the use of drone strikes, detainees at Guantanamo Bay prison facility, and American citizens who are terrorists.',
'duration': 11,
} }
} }
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(1) data = self._download_json(
json_url = "http://www.hark.com/clips/%s.json" %(video_id) 'http://www.hark.com/clips/%s.json' % video_id, video_id)
info_json = self._download_webpage(json_url, video_id)
info = json.loads(info_json)
final_url = info['url']
return {'id': video_id, return {
'url' : final_url, 'id': video_id,
'title': info['name'], 'url': data['url'],
'ext': determine_ext(final_url), 'title': data['name'],
'description': info['description'], 'description': data.get('description'),
'thumbnail': info['image_original'], 'thumbnail': data.get('image_original'),
'duration': info['duration'], 'duration': data.get('duration'),
} }

View File

@ -0,0 +1,75 @@
# coding: utf-8
from __future__ import unicode_literals
from .common import InfoExtractor
from ..utils import (
get_meta_content,
int_or_none,
parse_iso8601,
)
class HeiseIE(InfoExtractor):
_VALID_URL = r'''(?x)
https?://(?:www\.)?heise\.de/video/artikel/
.+?(?P<id>[0-9]+)\.html(?:$|[?#])
'''
_TEST = {
'url': (
'http://www.heise.de/video/artikel/Podcast-c-t-uplink-3-3-Owncloud-Tastaturen-Peilsender-Smartphone-2404147.html'
),
'md5': 'ffed432483e922e88545ad9f2f15d30e',
'info_dict': {
'id': '2404147',
'ext': 'mp4',
'title': (
"Podcast: c't uplink 3.3 Owncloud / Tastaturen / Peilsender Smartphone"
),
'format_id': 'mp4_720',
'timestamp': 1411812600,
'upload_date': '20140927',
'description': 'In uplink-Episode 3.3 geht es darum, wie man sich von Cloud-Anbietern emanzipieren kann, worauf man beim Kauf einer Tastatur achten sollte und was Smartphones über uns verraten.',
'thumbnail': 're:https?://.*\.jpg$',
}
}
def _real_extract(self, url):
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
container_id = self._search_regex(
r'<div class="videoplayerjw".*?data-container="([0-9]+)"',
webpage, 'container ID')
sequenz_id = self._search_regex(
r'<div class="videoplayerjw".*?data-sequenz="([0-9]+)"',
webpage, 'sequenz ID')
data_url = 'http://www.heise.de/videout/feed?container=%s&sequenz=%s' % (container_id, sequenz_id)
doc = self._download_xml(data_url, video_id)
info = {
'id': video_id,
'thumbnail': self._og_search_thumbnail(webpage),
'timestamp': parse_iso8601(get_meta_content('date', webpage)),
'description': self._og_search_description(webpage),
}
title = get_meta_content('fulltitle', webpage)
if title:
info['title'] = title
else:
info['title'] = self._og_search_title(webpage)
formats = []
for source_node in doc.findall('.//{http://rss.jwpcdn.com/}source'):
label = source_node.attrib['label']
height = int_or_none(self._search_regex(
r'^(.*?_)?([0-9]+)p$', label, 'height', default=None))
formats.append({
'url': source_node.attrib['file'],
'format_note': label,
'height': height,
})
self._sort_formats(formats)
info['formats'] = formats
return info

View File

@ -28,13 +28,13 @@ class HowStuffWorksIE(InfoExtractor):
} }
}, },
{ {
'url': 'http://adventure.howstuffworks.com/39516-deadliest-catch-jakes-farewell-pots-video.htm', 'url': 'http://adventure.howstuffworks.com/7199-survival-zone-food-and-water-in-the-savanna-video.htm',
'info_dict': { 'info_dict': {
'id': '553470', 'id': '453464',
'display_id': 'deadliest-catch-jakes-farewell-pots', 'display_id': 'survival-zone-food-and-water-in-the-savanna',
'ext': 'mp4', 'ext': 'mp4',
'title': 'Deadliest Catch: Jake\'s Farewell Pots', 'title': 'Survival Zone: Food and Water In the Savanna',
'description': 'md5:9632c346d5e43ee238028c9cefd8dbbc', 'description': 'md5:7e1c89f6411434970c15fa094170c371',
'thumbnail': 're:^https?://.*\.jpg$', 'thumbnail': 're:^https?://.*\.jpg$',
}, },
'params': { 'params': {

View File

@ -33,8 +33,7 @@ class HuffPostIE(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')
api_url = 'http://embed.live.huffingtonpost.com/api/segments/%s.json' % video_id api_url = 'http://embed.live.huffingtonpost.com/api/segments/%s.json' % video_id
data = self._download_json(api_url, video_id)['data'] data = self._download_json(api_url, video_id)['data']

View File

@ -1,7 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import re
from .common import InfoExtractor from .common import InfoExtractor
@ -20,13 +18,11 @@ class IconosquareIE(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)
html_title = self._html_search_regex( title = self._html_search_regex(
r'<title>(.+?)</title>', r'<title>(.+?)(?: *\(Videos?\))? \| (?:Iconosquare|Statigram)</title>',
webpage, 'title') webpage, 'title')
title = re.sub(r'(?: *\(Videos?\))? \| (?:Iconosquare|Statigram)$', '', html_title)
uploader_id = self._html_search_regex( uploader_id = self._html_search_regex(
r'@([^ ]+)', title, 'uploader name', fatal=False) r'@([^ ]+)', title, 'uploader name', fatal=False)

View File

@ -71,6 +71,7 @@ class IGNIE(InfoExtractor):
def _find_video_id(self, webpage): def _find_video_id(self, webpage):
res_id = [ res_id = [
r'"video_id"\s*:\s*"(.*?)"',
r'data-video-id="(.+?)"', r'data-video-id="(.+?)"',
r'<object id="vid_(.+?)"', r'<object id="vid_(.+?)"',
r'<meta name="og:image" content=".*/(.+?)-(.+?)/.+.jpg"', r'<meta name="og:image" content=".*/(.+?)-(.+?)/.+.jpg"',
@ -85,10 +86,15 @@ class IGNIE(InfoExtractor):
webpage = self._download_webpage(url, name_or_id) webpage = self._download_webpage(url, name_or_id)
if page_type != 'video': if page_type != 'video':
multiple_urls = re.findall( multiple_urls = re.findall(
'<param name="flashvars" value="[^"]*?url=(https?://www\.ign\.com/videos/.*?)["&]', '<param name="flashvars"[^>]*value="[^"]*?url=(https?://www\.ign\.com/videos/.*?)["&]',
webpage) webpage)
if multiple_urls: if multiple_urls:
return [self.url_result(u, ie='IGN') for u in multiple_urls] entries = [self.url_result(u, ie='IGN') for u in multiple_urls]
return {
'_type': 'playlist',
'id': name_or_id,
'entries': entries,
}
video_id = self._find_video_id(webpage) video_id = self._find_video_id(webpage)
result = self._get_video_info(video_id) result = self._get_video_info(video_id)
@ -111,13 +117,13 @@ class IGNIE(InfoExtractor):
class OneUPIE(IGNIE): class OneUPIE(IGNIE):
_VALID_URL = r'https?://gamevideos\.1up\.com/(?P<type>video)/id/(?P<name_or_id>.+)' _VALID_URL = r'https?://gamevideos\.1up\.com/(?P<type>video)/id/(?P<name_or_id>.+)\.html'
IE_NAME = '1up.com' IE_NAME = '1up.com'
_DESCRIPTION_RE = r'<div id="vid_summary">(.+?)</div>' _DESCRIPTION_RE = r'<div id="vid_summary">(.+?)</div>'
_TESTS = [{ _TESTS = [{
'url': 'http://gamevideos.1up.com/video/id/34976', 'url': 'http://gamevideos.1up.com/video/id/34976.html',
'md5': '68a54ce4ebc772e4b71e3123d413163d', 'md5': '68a54ce4ebc772e4b71e3123d413163d',
'info_dict': { 'info_dict': {
'id': '34976', 'id': '34976',

View File

@ -1,3 +1,5 @@
from __future__ import unicode_literals
import re import re
from .common import InfoExtractor from .common import InfoExtractor
@ -12,12 +14,13 @@ class InternetVideoArchiveIE(InfoExtractor):
_VALID_URL = r'https?://video\.internetvideoarchive\.net/flash/players/.*?\?.*?publishedid.*?' _VALID_URL = r'https?://video\.internetvideoarchive\.net/flash/players/.*?\?.*?publishedid.*?'
_TEST = { _TEST = {
u'url': u'http://video.internetvideoarchive.net/flash/players/flashconfiguration.aspx?customerid=69249&publishedid=452693&playerid=247', 'url': 'http://video.internetvideoarchive.net/flash/players/flashconfiguration.aspx?customerid=69249&publishedid=452693&playerid=247',
u'file': u'452693.mp4', 'info_dict': {
u'info_dict': { 'id': '452693',
u'title': u'SKYFALL', 'ext': 'mp4',
u'description': u'In SKYFALL, Bond\'s loyalty to M is tested as her past comes back to haunt her. As MI6 comes under attack, 007 must track down and destroy the threat, no matter how personal the cost.', 'title': 'SKYFALL',
u'duration': 153, 'description': 'In SKYFALL, Bond\'s loyalty to M is tested as her past comes back to haunt her. As MI6 comes under attack, 007 must track down and destroy the threat, no matter how personal the cost.',
'duration': 149,
}, },
} }
@ -42,7 +45,7 @@ class InternetVideoArchiveIE(InfoExtractor):
url = self._build_url(query) url = self._build_url(query)
flashconfiguration = self._download_xml(url, video_id, flashconfiguration = self._download_xml(url, video_id,
u'Downloading flash configuration') 'Downloading flash configuration')
file_url = flashconfiguration.find('file').text file_url = flashconfiguration.find('file').text
file_url = file_url.replace('/playlist.aspx', '/mrssplaylist.aspx') file_url = file_url.replace('/playlist.aspx', '/mrssplaylist.aspx')
# Replace some of the parameters in the query to get the best quality # Replace some of the parameters in the query to get the best quality
@ -51,7 +54,7 @@ class InternetVideoArchiveIE(InfoExtractor):
lambda m: self._clean_query(m.group()), lambda m: self._clean_query(m.group()),
file_url) file_url)
info = self._download_xml(file_url, video_id, info = self._download_xml(file_url, video_id,
u'Downloading video info') 'Downloading video info')
item = info.find('channel/item') item = info.find('channel/item')
def _bp(p): def _bp(p):

View File

@ -63,7 +63,8 @@ class IzleseneIE(InfoExtractor):
title = self._og_search_title(webpage) title = self._og_search_title(webpage)
description = self._og_search_description(webpage) description = self._og_search_description(webpage)
thumbnail = self._og_search_thumbnail(webpage) thumbnail = self._proto_relative_url(
self._og_search_thumbnail(webpage), scheme='http:')
uploader = self._html_search_regex( uploader = self._html_search_regex(
r"adduserUsername\s*=\s*'([^']+)';", r"adduserUsername\s*=\s*'([^']+)';",

View File

@ -1,8 +1,6 @@
# coding=utf-8 # coding=utf-8
from __future__ import unicode_literals from __future__ import unicode_literals
import re
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import ( from ..utils import (
int_or_none, int_or_none,
@ -12,14 +10,14 @@ from ..utils import (
class JpopsukiIE(InfoExtractor): class JpopsukiIE(InfoExtractor):
IE_NAME = 'jpopsuki.tv' IE_NAME = 'jpopsuki.tv'
_VALID_URL = r'https?://(?:www\.)?jpopsuki\.tv/video/(.*?)/(?P<id>\S+)' _VALID_URL = r'https?://(?:www\.)?jpopsuki\.tv/(?:category/)?video/[^/]+/(?P<id>\S+)'
_TEST = { _TEST = {
'url': 'http://www.jpopsuki.tv/video/ayumi-hamasaki---evolution/00be659d23b0b40508169cdee4545771', 'url': 'http://www.jpopsuki.tv/video/ayumi-hamasaki---evolution/00be659d23b0b40508169cdee4545771',
'md5': '88018c0c1a9b1387940e90ec9e7e198e', 'md5': '88018c0c1a9b1387940e90ec9e7e198e',
'file': '00be659d23b0b40508169cdee4545771.mp4',
'info_dict': { 'info_dict': {
'id': '00be659d23b0b40508169cdee4545771', 'id': '00be659d23b0b40508169cdee4545771',
'ext': 'mp4',
'title': 'ayumi hamasaki - evolution', 'title': 'ayumi hamasaki - evolution',
'description': 'Release date: 2001.01.31\r\n浜崎あゆみ - evolution', 'description': 'Release date: 2001.01.31\r\n浜崎あゆみ - evolution',
'thumbnail': 'http://www.jpopsuki.tv/cache/89722c74d2a2ebe58bcac65321c115b2.jpg', 'thumbnail': 'http://www.jpopsuki.tv/cache/89722c74d2a2ebe58bcac65321c115b2.jpg',
@ -30,8 +28,7 @@ class JpopsukiIE(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)
@ -47,11 +44,9 @@ class JpopsukiIE(InfoExtractor):
uploader_id = self._html_search_regex( uploader_id = self._html_search_regex(
r'<li>from: <a href="/user/view/user/\S*?/uid/(\d*)', r'<li>from: <a href="/user/view/user/\S*?/uid/(\d*)',
webpage, 'video uploader_id', fatal=False) webpage, 'video uploader_id', fatal=False)
upload_date = self._html_search_regex( upload_date = unified_strdate(self._html_search_regex(
r'<li>uploaded: (.*?)</li>', webpage, 'video upload_date', r'<li>uploaded: (.*?)</li>', webpage, 'video upload_date',
fatal=False) fatal=False))
if upload_date is not None:
upload_date = unified_strdate(upload_date)
view_count_str = self._html_search_regex( view_count_str = self._html_search_regex(
r'<li>Hits: ([0-9]+?)</li>', webpage, 'video view_count', r'<li>Hits: ([0-9]+?)</li>', webpage, 'video view_count',
fatal=False) fatal=False)

View File

@ -11,10 +11,9 @@ from ..utils import (
class JukeboxIE(InfoExtractor): class JukeboxIE(InfoExtractor):
_VALID_URL = r'^http://www\.jukebox?\..+?\/.+[,](?P<video_id>[a-z0-9\-]+)\.html' _VALID_URL = r'^http://www\.jukebox?\..+?\/.+[,](?P<id>[a-z0-9\-]+)\.html'
_TEST = { _TEST = {
'url': 'http://www.jukebox.es/kosheen/videoclip,pride,r303r.html', 'url': 'http://www.jukebox.es/kosheen/videoclip,pride,r303r.html',
'md5': '1574e9b4d6438446d5b7dbcdf2786276',
'info_dict': { 'info_dict': {
'id': 'r303r', 'id': 'r303r',
'ext': 'flv', 'ext': 'flv',
@ -24,8 +23,7 @@ class JukeboxIE(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('video_id')
html = self._download_webpage(url, video_id) html = self._download_webpage(url, video_id)
iframe_url = unescapeHTML(self._search_regex(r'<iframe .*src="([^"]*)"', html, 'iframe url')) iframe_url = unescapeHTML(self._search_regex(r'<iframe .*src="([^"]*)"', html, 'iframe url'))

View File

@ -1,155 +0,0 @@
from __future__ import unicode_literals
import itertools
import json
import os
import re
from .common import InfoExtractor
from ..utils import (
compat_str,
ExtractorError,
formatSeconds,
)
class JustinTVIE(InfoExtractor):
"""Information extractor for justin.tv and twitch.tv"""
# TODO: One broadcast may be split into multiple videos. The key
# 'broadcast_id' is the same for all parts, and 'broadcast_part'
# starts at 1 and increases. Can we treat all parts as one video?
_VALID_URL = r"""(?x)^(?:http://)?(?:www\.)?(?:twitch|justin)\.tv/
(?:
(?P<channelid>[^/]+)|
(?:(?:[^/]+)/b/(?P<videoid>[^/]+))|
(?:(?:[^/]+)/c/(?P<chapterid>[^/]+))
)
/?(?:\#.*)?$
"""
_JUSTIN_PAGE_LIMIT = 100
IE_NAME = 'justin.tv'
IE_DESC = 'justin.tv and twitch.tv'
_TEST = {
'url': 'http://www.twitch.tv/thegamedevhub/b/296128360',
'md5': 'ecaa8a790c22a40770901460af191c9a',
'info_dict': {
'id': '296128360',
'ext': 'flv',
'upload_date': '20110927',
'uploader_id': 25114803,
'uploader': 'thegamedevhub',
'title': 'Beginner Series - Scripting With Python Pt.1'
}
}
# Return count of items, list of *valid* items
def _parse_page(self, url, video_id, counter):
info_json = self._download_webpage(
url, video_id,
'Downloading video info JSON on page %d' % counter,
'Unable to download video info JSON %d' % counter)
response = json.loads(info_json)
if type(response) != list:
error_text = response.get('error', 'unknown error')
raise ExtractorError('Justin.tv API: %s' % error_text)
info = []
for clip in response:
video_url = clip['video_file_url']
if video_url:
video_extension = os.path.splitext(video_url)[1][1:]
video_date = re.sub('-', '', clip['start_time'][:10])
video_uploader_id = clip.get('user_id', clip.get('channel_id'))
video_id = clip['id']
video_title = clip.get('title', video_id)
info.append({
'id': compat_str(video_id),
'url': video_url,
'title': video_title,
'uploader': clip.get('channel_name', video_uploader_id),
'uploader_id': video_uploader_id,
'upload_date': video_date,
'ext': video_extension,
})
return (len(response), info)
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
api_base = 'http://api.justin.tv'
paged = False
if mobj.group('channelid'):
paged = True
video_id = mobj.group('channelid')
api = api_base + '/channel/archives/%s.json' % video_id
elif mobj.group('chapterid'):
chapter_id = mobj.group('chapterid')
webpage = self._download_webpage(url, chapter_id)
m = re.search(r'PP\.archive_id = "([0-9]+)";', webpage)
if not m:
raise ExtractorError('Cannot find archive of a chapter')
archive_id = m.group(1)
api = api_base + '/broadcast/by_chapter/%s.xml' % chapter_id
doc = self._download_xml(
api, chapter_id,
note='Downloading chapter information',
errnote='Chapter information download failed')
for a in doc.findall('.//archive'):
if archive_id == a.find('./id').text:
break
else:
raise ExtractorError('Could not find chapter in chapter information')
video_url = a.find('./video_file_url').text
video_ext = video_url.rpartition('.')[2] or 'flv'
chapter_api_url = 'https://api.twitch.tv/kraken/videos/c' + chapter_id
chapter_info = self._download_json(
chapter_api_url, 'c' + chapter_id,
note='Downloading chapter metadata',
errnote='Download of chapter metadata failed')
bracket_start = int(doc.find('.//bracket_start').text)
bracket_end = int(doc.find('.//bracket_end').text)
# TODO determine start (and probably fix up file)
# youtube-dl -v http://www.twitch.tv/firmbelief/c/1757457
#video_url += '?start=' + TODO:start_timestamp
# bracket_start is 13290, but we want 51670615
self._downloader.report_warning('Chapter detected, but we can just download the whole file. '
'Chapter starts at %s and ends at %s' % (formatSeconds(bracket_start), formatSeconds(bracket_end)))
info = {
'id': 'c' + chapter_id,
'url': video_url,
'ext': video_ext,
'title': chapter_info['title'],
'thumbnail': chapter_info['preview'],
'description': chapter_info['description'],
'uploader': chapter_info['channel']['display_name'],
'uploader_id': chapter_info['channel']['name'],
}
return info
else:
video_id = mobj.group('videoid')
api = api_base + '/broadcast/by_archive/%s.json' % video_id
entries = []
offset = 0
limit = self._JUSTIN_PAGE_LIMIT
for counter in itertools.count(1):
page_url = api + ('?offset=%d&limit=%d' % (offset, limit))
page_count, page_info = self._parse_page(
page_url, video_id, counter)
entries.extend(page_info)
if not paged or page_count != limit:
break
offset += limit
return {
'_type': 'playlist',
'id': video_id,
'entries': entries,
}

View File

@ -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,22 +19,17 @@ class KickStarterIE(InfoExtractor):
}, { }, {
'note': 'Embedded video (not using the native kickstarter video service)', 'note': 'Embedded video (not using the native kickstarter video service)',
'url': 'https://www.kickstarter.com/projects/597507018/pebble-e-paper-watch-for-iphone-and-android/posts/659178', 'url': 'https://www.kickstarter.com/projects/597507018/pebble-e-paper-watch-for-iphone-and-android/posts/659178',
'playlist': [ 'info_dict': {
{ 'id': '78704821',
'info_dict': { 'ext': 'mp4',
'id': '78704821', 'uploader_id': 'pebble',
'ext': 'mp4', 'uploader': 'Pebble Technology',
'uploader_id': 'pebble', 'title': 'Pebble iOS Notifications',
'uploader': 'Pebble Technology', }
'title': 'Pebble iOS Notifications',
}
}
],
}] }]
def _real_extract(self, url): def _real_extract(self, url):
m = re.match(self._VALID_URL, url) video_id = self._match_id(url)
video_id = m.group('id')
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
title = self._html_search_regex( title = self._html_search_regex(

View File

@ -34,7 +34,7 @@ class KontrTubeIE(InfoExtractor):
video_url = self._html_search_regex(r"video_url: '(.+?)/?',", webpage, 'video URL') video_url = self._html_search_regex(r"video_url: '(.+?)/?',", webpage, 'video URL')
thumbnail = self._html_search_regex(r"preview_url: '(.+?)/?',", webpage, 'video thumbnail', fatal=False) thumbnail = self._html_search_regex(r"preview_url: '(.+?)/?',", webpage, 'video thumbnail', fatal=False)
title = self._html_search_regex( title = self._html_search_regex(
r'<title>(.+?) - Труба зовёт - Интересный видеохостинг</title>', webpage, 'video title') r'<title>(.+?)</title>', webpage, 'video title')
description = self._html_search_meta('description', webpage, 'video description') description = self._html_search_meta('description', webpage, 'video description')
mobj = re.search( mobj = re.search(

View File

@ -1,7 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import re
from .common import InfoExtractor from .common import InfoExtractor
@ -18,11 +16,11 @@ class Ku6IE(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)
title = self._search_regex(r'<h1 title=.*>(.*?)</h1>', webpage, 'title')
title = self._html_search_regex(
r'<h1 title=.*>(.*?)</h1>', webpage, 'title')
dataUrl = 'http://v.ku6.com/fetchVideo4Player/%s.html' % video_id dataUrl = 'http://v.ku6.com/fetchVideo4Player/%s.html' % video_id
jsonData = self._download_json(dataUrl, video_id) jsonData = self._download_json(dataUrl, video_id)
downloadUrl = jsonData['data']['f'] downloadUrl = jsonData['data']['f']

View File

@ -0,0 +1,77 @@
from __future__ import unicode_literals
import random
import re
from .common import InfoExtractor
class Laola1TvIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?laola1\.tv/(?P<lang>[a-z]+)-(?P<portal>[a-z]+)/.*?/(?P<id>[0-9]+)\.html'
_TEST = {
'url': 'http://www.laola1.tv/de-de/live/bwf-bitburger-open-grand-prix-gold-court-1/250019.html',
'info_dict': {
'id': '250019',
'ext': 'mp4',
'title': 'Bitburger Open Grand Prix Gold - Court 1',
'categories': ['Badminton'],
'uploader': 'BWF - Badminton World Federation',
'is_live': True,
},
'params': {
'skip_download': True,
}
}
_BROKEN = True # Not really - extractor works fine, but f4m downloader does not support live streams yet.
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
video_id = mobj.group('id')
lang = mobj.group('lang')
portal = mobj.group('portal')
webpage = self._download_webpage(url, video_id)
iframe_url = self._search_regex(
r'<iframe[^>]*?class="main_tv_player"[^>]*?src="([^"]+)"',
webpage, 'iframe URL')
iframe = self._download_webpage(
iframe_url, video_id, note='Downloading iframe')
flashvars_m = re.findall(
r'flashvars\.([_a-zA-Z0-9]+)\s*=\s*"([^"]*)";', iframe)
flashvars = dict((m[0], m[1]) for m in flashvars_m)
xml_url = ('http://www.laola1.tv/server/hd_video.php?' +
'play=%s&partner=1&portal=%s&v5ident=&lang=%s' % (
video_id, portal, lang))
hd_doc = self._download_xml(xml_url, video_id)
title = hd_doc.find('.//video/title').text
flash_url = hd_doc.find('.//video/url').text
categories = hd_doc.find('.//video/meta_sports').text.split(',')
uploader = hd_doc.find('.//video/meta_organistation').text
ident = random.randint(10000000, 99999999)
token_url = '%s&ident=%s&klub=0&unikey=0&timestamp=%s&auth=%s' % (
flash_url, ident, flashvars['timestamp'], flashvars['auth'])
token_doc = self._download_xml(
token_url, video_id, note='Downloading token')
token_attrib = token_doc.find('.//token').attrib
if token_attrib.get('auth') == 'blocked':
raise ExtractorError('Token error: ' % token_attrib.get('comment'))
video_url = '%s?hdnea=%s&hdcore=3.2.0' % (
token_attrib['url'], token_attrib['auth'])
return {
'id': video_id,
'is_live': True,
'title': title,
'url': video_url,
'uploader': uploader,
'categories': categories,
'ext': 'mp4',
}

View File

@ -190,7 +190,8 @@ class LivestreamOriginalIE(InfoExtractor):
'id': video_id, 'id': video_id,
'title': item.find('title').text, 'title': item.find('title').text,
'url': 'rtmp://extondemand.livestream.com/ondemand', 'url': 'rtmp://extondemand.livestream.com/ondemand',
'play_path': 'mp4:trans/dv15/mogulus-{0}.mp4'.format(path), 'play_path': 'trans/dv15/mogulus-{0}'.format(path),
'player_url': 'http://static.livestream.com/chromelessPlayer/v21/playerapi.swf?hash=5uetk&v=0803&classid=D27CDB6E-AE6D-11cf-96B8-444553540000&jsEnabled=false&wmode=opaque',
'ext': 'flv', 'ext': 'flv',
'thumbnail': thumbnail_url, 'thumbnail': thumbnail_url,
} }

View File

@ -0,0 +1,67 @@
# coding: utf-8
from __future__ import unicode_literals
import re
import json
from .common import InfoExtractor
from ..utils import (
determine_ext,
js_to_json,
parse_duration,
remove_end,
)
class LRTIE(InfoExtractor):
IE_NAME = 'lrt.lt'
_VALID_URL = r'https?://(?:www\.)?lrt\.lt/mediateka/irasas/(?P<id>[0-9]+)'
_TEST = {
'url': 'http://www.lrt.lt/mediateka/irasas/54391/',
'info_dict': {
'id': '54391',
'ext': 'mp4',
'title': 'Septynios Kauno dienos',
'description': 'md5:24d84534c7dc76581e59f5689462411a',
'duration': 1783,
},
'params': {
'skip_download': True, # HLS download
},
}
def _real_extract(self, url):
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
title = remove_end(self._og_search_title(webpage), ' - LRT')
thumbnail = self._og_search_thumbnail(webpage)
description = self._og_search_description(webpage)
duration = parse_duration(self._search_regex(
r"'duration':\s*'([^']+)',", webpage,
'duration', fatal=False, default=None))
formats = []
for js in re.findall(r'(?s)config:\s*(\{.*?\})', webpage):
data = json.loads(js_to_json(js))
if data['provider'] == 'rtmp':
formats.append({
'format_id': 'rtmp',
'ext': determine_ext(data['file']),
'url': data['streamer'],
'play_path': 'mp4:%s' % data['file'],
'preference': -1,
})
else:
formats.extend(
self._extract_m3u8_formats(data['file'], video_id, 'mp4'))
return {
'id': video_id,
'title': title,
'formats': formats,
'thumbnail': thumbnail,
'description': description,
'duration': duration,
}

View File

@ -0,0 +1,87 @@
# coding: utf-8
from __future__ import unicode_literals
import re
from .common import InfoExtractor
from ..utils import (
ExtractorError,
qualities,
unified_strdate,
)
class MgoonIE(InfoExtractor):
_VALID_URL = r'''(?x)https?://(?:www\.)?
(?:(:?m\.)?mgoon\.com/(?:ch/(?:.+)/v|play/view)|
video\.mgoon\.com)/(?P<id>[0-9]+)'''
_API_URL = 'http://mpos.mgoon.com/player/video?id={0:}'
_TESTS = [
{
'url': 'http://m.mgoon.com/ch/hi6618/v/5582148',
'md5': 'dd46bb66ab35cf6d51cc812fd82da79d',
'info_dict': {
'id': '5582148',
'uploader_id': 'hi6618',
'duration': 240.419,
'upload_date': '20131220',
'ext': 'mp4',
'title': 'md5:543aa4c27a4931d371c3f433e8cebebc',
'thumbnail': 're:^https?://.*\.jpg$',
}
},
{
'url': 'http://www.mgoon.com/play/view/5582148',
'only_matching': True,
},
{
'url': 'http://video.mgoon.com/5582148',
'only_matching': True,
},
]
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
video_id = mobj.group('id')
data = self._download_json(self._API_URL.format(video_id), video_id)
if data.get('errorInfo', {}).get('code') != 'NONE':
raise ExtractorError('%s encountered an error: %s' % (
self.IE_NAME, data['errorInfo']['message']), expected=True)
v_info = data['videoInfo']
title = v_info.get('v_title')
thumbnail = v_info.get('v_thumbnail')
duration = v_info.get('v_duration')
upload_date = unified_strdate(v_info.get('v_reg_date'))
uploader_id = data.get('userInfo', {}).get('u_alias')
if duration:
duration /= 1000.0
age_limit = None
if data.get('accessInfo', {}).get('code') == 'VIDEO_STATUS_ADULT':
age_limit = 18
formats = []
get_quality = qualities(['360p', '480p', '720p', '1080p'])
for fmt in data['videoFiles']:
formats.append({
'format_id': fmt['label'],
'quality': get_quality(fmt['label']),
'url': fmt['url'],
'ext': fmt['format'],
})
self._sort_formats(formats)
return {
'id': video_id,
'title': title,
'formats': formats,
'thumbnail': thumbnail,
'duration': duration,
'upload_date': upload_date,
'uploader_id': uploader_id,
'age_limit': age_limit,
}

View File

@ -6,6 +6,7 @@ import json
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import ( from ..utils import (
compat_urllib_parse, compat_urllib_parse,
compat_urlparse,
get_element_by_attribute, get_element_by_attribute,
parse_duration, parse_duration,
strip_jsonp, strip_jsonp,
@ -39,13 +40,21 @@ class MiTeleIE(InfoExtractor):
).replace('\'', '"') ).replace('\'', '"')
embed_data = json.loads(embed_data_json) embed_data = json.loads(embed_data_json)
info_url = embed_data['flashvars']['host'] domain = embed_data['mediaUrl']
if not domain.startswith('http'):
# only happens in telecinco.es videos
domain = 'http://' + domain
info_url = compat_urlparse.urljoin(
domain,
compat_urllib_parse.unquote(embed_data['flashvars']['host'])
)
info_el = self._download_xml(info_url, episode).find('./video/info') info_el = self._download_xml(info_url, episode).find('./video/info')
video_link = info_el.find('videoUrl/link').text video_link = info_el.find('videoUrl/link').text
token_query = compat_urllib_parse.urlencode({'id': video_link}) token_query = compat_urllib_parse.urlencode({'id': video_link})
token_info = self._download_json( token_info = self._download_json(
'http://token.mitele.es/?' + token_query, episode, embed_data['flashvars']['ov_tk'] + '?' + token_query,
episode,
transform_source=strip_jsonp transform_source=strip_jsonp
) )

View File

@ -33,22 +33,22 @@ class MixcloudIE(InfoExtractor):
}, },
} }
def check_urls(self, url_list): def _get_url(self, track_id, template_url):
"""Returns 1st active url from list""" server_count = 30
for url in url_list: for i in range(server_count):
url = template_url % i
try: try:
# We only want to know if the request succeed # We only want to know if the request succeed
# don't download the whole file # don't download the whole file
self._request_webpage(HEADRequest(url), None, False) self._request_webpage(
HEADRequest(url), track_id,
'Checking URL %d/%d ...' % (i + 1, server_count + 1))
return url return url
except ExtractorError: except ExtractorError:
url = None pass
return None return None
def _get_url(self, template_url):
return self.check_urls(template_url % i for i in range(30))
def _real_extract(self, url): def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
uploader = mobj.group(1) uploader = mobj.group(1)
@ -61,16 +61,16 @@ class MixcloudIE(InfoExtractor):
r'\s(?:data-preview-url|m-preview)="(.+?)"', webpage, 'preview url') r'\s(?:data-preview-url|m-preview)="(.+?)"', webpage, 'preview url')
song_url = preview_url.replace('/previews/', '/c/originals/') song_url = preview_url.replace('/previews/', '/c/originals/')
template_url = re.sub(r'(stream\d*)', 'stream%d', song_url) template_url = re.sub(r'(stream\d*)', 'stream%d', song_url)
final_song_url = self._get_url(template_url) final_song_url = self._get_url(track_id, template_url)
if final_song_url is None: if final_song_url is None:
self.to_screen('Trying with m4a extension') self.to_screen('Trying with m4a extension')
template_url = template_url.replace('.mp3', '.m4a').replace('originals/', 'm4a/64/') template_url = template_url.replace('.mp3', '.m4a').replace('originals/', 'm4a/64/')
final_song_url = self._get_url(template_url) final_song_url = self._get_url(track_id, template_url)
if final_song_url is None: if final_song_url is None:
raise ExtractorError('Unable to extract track url') raise ExtractorError('Unable to extract track url')
PREFIX = ( PREFIX = (
r'<div class="cloudcast-play-button-container"' r'<div class="cloudcast-play-button-container[^"]*?"'
r'(?:\s+[a-zA-Z0-9-]+(?:="[^"]+")?)*?\s+') r'(?:\s+[a-zA-Z0-9-]+(?:="[^"]+")?)*?\s+')
title = self._html_search_regex( title = self._html_search_regex(
PREFIX + r'm-title="([^"]+)"', webpage, 'title') PREFIX + r'm-title="([^"]+)"', webpage, 'title')

View File

@ -6,12 +6,11 @@ from .common import InfoExtractor
from ..utils import ( from ..utils import (
parse_duration, parse_duration,
parse_iso8601, parse_iso8601,
find_xpath_attr,
) )
class MLBIE(InfoExtractor): class MLBIE(InfoExtractor):
_VALID_URL = r'https?://m\.mlb\.com/(?:(?:.*?/)?video/(?:topic/[\da-z_-]+/)?v|shared/video/embed/embed\.html\?.*?\bcontent_id=)(?P<id>n?\d+)' _VALID_URL = r'https?://m(?:lb)?\.mlb\.com/(?:(?:.*?/)?video/(?:topic/[\da-z_-]+/)?v|(?:shared/video/embed/embed\.html|[^/]+/video/play\.jsp)\?.*?\bcontent_id=)(?P<id>n?\d+)'
_TESTS = [ _TESTS = [
{ {
'url': 'http://m.mlb.com/sea/video/topic/51231442/v34698933/nymsea-ackley-robs-a-home-run-with-an-amazing-catch/?c_id=sea', 'url': 'http://m.mlb.com/sea/video/topic/51231442/v34698933/nymsea-ackley-robs-a-home-run-with-an-amazing-catch/?c_id=sea',
@ -73,6 +72,14 @@ class MLBIE(InfoExtractor):
'url': 'http://m.mlb.com/shared/video/embed/embed.html?content_id=35692085&topic_id=6479266&width=400&height=224&property=mlb', 'url': 'http://m.mlb.com/shared/video/embed/embed.html?content_id=35692085&topic_id=6479266&width=400&height=224&property=mlb',
'only_matching': True, 'only_matching': True,
}, },
{
'url': 'http://mlb.mlb.com/shared/video/embed/embed.html?content_id=36599553',
'only_matching': True,
},
{
'url': 'http://mlb.mlb.com/es/video/play.jsp?content_id=36599553',
'only_matching': True,
},
] ]
def _real_extract(self, url): def _real_extract(self, url):
@ -88,8 +95,9 @@ class MLBIE(InfoExtractor):
duration = parse_duration(detail.find('./duration').text) duration = parse_duration(detail.find('./duration').text)
timestamp = parse_iso8601(detail.attrib['date'][:-5]) timestamp = parse_iso8601(detail.attrib['date'][:-5])
thumbnail = find_xpath_attr( thumbnails = [{
detail, './thumbnailScenarios/thumbnailScenario', 'type', '45').text 'url': thumbnail.text,
} for thumbnail in detail.findall('./thumbnailScenarios/thumbnailScenario')]
formats = [] formats = []
for media_url in detail.findall('./url'): for media_url in detail.findall('./url'):
@ -116,5 +124,5 @@ class MLBIE(InfoExtractor):
'duration': duration, 'duration': duration,
'timestamp': timestamp, 'timestamp': timestamp,
'formats': formats, 'formats': formats,
'thumbnail': thumbnail, 'thumbnails': thumbnails,
} }

View File

@ -0,0 +1,70 @@
# coding: utf-8
from __future__ import unicode_literals
import os.path
import re
from .common import InfoExtractor
from ..utils import (
compat_urllib_parse,
compat_urllib_request,
)
class MonikerIE(InfoExtractor):
IE_DESC = 'allmyvideos.net and vidspot.net'
_VALID_URL = r'https?://(?:www\.)?(?:allmyvideos|vidspot)\.net/(?P<id>[a-zA-Z0-9_-]+)'
_TESTS = [{
'url': 'http://allmyvideos.net/jih3nce3x6wn',
'md5': '710883dee1bfc370ecf9fa6a89307c88',
'info_dict': {
'id': 'jih3nce3x6wn',
'ext': 'mp4',
'title': 'youtube-dl test video',
},
}, {
'url': 'http://vidspot.net/l2ngsmhs8ci5',
'md5': '710883dee1bfc370ecf9fa6a89307c88',
'info_dict': {
'id': 'l2ngsmhs8ci5',
'ext': 'mp4',
'title': 'youtube-dl test video',
},
}, {
'url': 'https://www.vidspot.net/l2ngsmhs8ci5',
'only_matching': True,
}]
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
video_id = mobj.group('id')
orig_webpage = self._download_webpage(url, video_id)
fields = re.findall(r'type="hidden" name="(.+?)"\s* value="?(.+?)">', orig_webpage)
data = dict(fields)
post = compat_urllib_parse.urlencode(data)
headers = {
b'Content-Type': b'application/x-www-form-urlencoded',
}
req = compat_urllib_request.Request(url, post, headers)
webpage = self._download_webpage(
req, video_id, note='Downloading video page ...')
title = os.path.splitext(data['fname'])[0]
#Could be several links with different quality
links = re.findall(r'"file" : "?(.+?)",', webpage)
# Assume the links are ordered in quality
formats = [{
'url': l,
'quality': i,
} for i, l in enumerate(links)]
self._sort_formats(formats)
return {
'id': video_id,
'title': title,
'formats': formats,
}

View File

@ -5,20 +5,20 @@ import re
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import ( from ..utils import (
int_or_none, str_to_int,
unified_strdate, unified_strdate,
) )
class MotherlessIE(InfoExtractor): class MotherlessIE(InfoExtractor):
_VALID_URL = r'http://(?:www\.)?motherless\.com/(?P<id>[A-Z0-9]+)' _VALID_URL = r'http://(?:www\.)?motherless\.com/(?:g/[a-z0-9_]+/)?(?P<id>[A-Z0-9]+)'
_TESTS = [ _TESTS = [
{ {
'url': 'http://motherless.com/AC3FFE1', 'url': 'http://motherless.com/AC3FFE1',
'md5': '5527fef81d2e529215dad3c2d744a7d9', 'md5': '310f62e325a9fafe64f68c0bccb6e75f',
'info_dict': { 'info_dict': {
'id': 'AC3FFE1', 'id': 'AC3FFE1',
'ext': 'flv', 'ext': 'mp4',
'title': 'Fucked in the ass while playing PS3', 'title': 'Fucked in the ass while playing PS3',
'categories': ['Gaming', 'anal', 'reluctant', 'rough', 'Wife'], 'categories': ['Gaming', 'anal', 'reluctant', 'rough', 'Wife'],
'upload_date': '20100913', 'upload_date': '20100913',
@ -40,33 +40,51 @@ class MotherlessIE(InfoExtractor):
'thumbnail': 're:http://.*\.jpg', 'thumbnail': 're:http://.*\.jpg',
'age_limit': 18, 'age_limit': 18,
} }
},
{
'url': 'http://motherless.com/g/cosplay/633979F',
'md5': '0b2a43f447a49c3e649c93ad1fafa4a0',
'info_dict': {
'id': '633979F',
'ext': 'mp4',
'title': 'Turtlette',
'categories': ['superheroine heroine superher'],
'upload_date': '20140827',
'uploader_id': 'shade0230',
'thumbnail': 're:http://.*\.jpg',
'age_limit': 18,
}
} }
] ]
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)
title = self._html_search_regex(r'id="view-upload-title">\s+([^<]+)<', webpage, 'title') title = self._html_search_regex(
r'id="view-upload-title">\s+([^<]+)<', webpage, 'title')
video_url = self._html_search_regex(r'setup\(\{\s+"file".+: "([^"]+)",', webpage, 'video_url') video_url = self._html_search_regex(
r'setup\(\{\s+"file".+: "([^"]+)",', webpage, 'video URL')
age_limit = self._rta_search(webpage) age_limit = self._rta_search(webpage)
view_count = str_to_int(self._html_search_regex(
r'<strong>Views</strong>\s+([^<]+)<',
webpage, 'view count', fatal=False))
like_count = str_to_int(self._html_search_regex(
r'<strong>Favorited</strong>\s+([^<]+)<',
webpage, 'like count', fatal=False))
view_count = self._html_search_regex(r'<strong>Views</strong>\s+([^<]+)<', webpage, 'view_count') upload_date = self._html_search_regex(
r'<strong>Uploaded</strong>\s+([^<]+)<', webpage, 'upload date')
upload_date = self._html_search_regex(r'<strong>Uploaded</strong>\s+([^<]+)<', webpage, 'upload_date')
if 'Ago' in upload_date: if 'Ago' in upload_date:
days = int(re.search(r'([0-9]+)', upload_date).group(1)) days = int(re.search(r'([0-9]+)', upload_date).group(1))
upload_date = (datetime.datetime.now() - datetime.timedelta(days=days)).strftime('%Y%m%d') upload_date = (datetime.datetime.now() - datetime.timedelta(days=days)).strftime('%Y%m%d')
else: else:
upload_date = unified_strdate(upload_date) upload_date = unified_strdate(upload_date)
like_count = self._html_search_regex(r'<strong>Favorited</strong>\s+([^<]+)<', webpage, 'like_count')
comment_count = webpage.count('class="media-comment-contents"') comment_count = webpage.count('class="media-comment-contents"')
uploader_id = self._html_search_regex(r'"thumb-member-username">\s+<a href="/m/([^"]+)"', webpage, 'uploader_id') uploader_id = self._html_search_regex(
r'"thumb-member-username">\s+<a href="/m/([^"]+)"',
webpage, 'uploader_id')
categories = self._html_search_meta('keywords', webpage) categories = self._html_search_meta('keywords', webpage)
if categories: if categories:
@ -79,8 +97,8 @@ class MotherlessIE(InfoExtractor):
'uploader_id': uploader_id, 'uploader_id': uploader_id,
'thumbnail': self._og_search_thumbnail(webpage), 'thumbnail': self._og_search_thumbnail(webpage),
'categories': categories, 'categories': categories,
'view_count': int_or_none(view_count.replace(',', '')), 'view_count': view_count,
'like_count': int_or_none(like_count.replace(',', '')), 'like_count': like_count,
'comment_count': comment_count, 'comment_count': comment_count,
'age_limit': age_limit, 'age_limit': age_limit,
'url': video_url, 'url': video_url,

View File

@ -0,0 +1,76 @@
# coding: utf-8
from __future__ import unicode_literals
import json
from .common import InfoExtractor
from ..utils import (
determine_ext,
int_or_none,
js_to_json,
)
class MuenchenTVIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?muenchen\.tv/livestream'
IE_DESC = 'münchen.tv'
_TEST = {
'url': 'http://www.muenchen.tv/livestream/',
'info_dict': {
'id': '5334',
'display_id': 'live',
'ext': 'mp4',
'title': 're:^münchen.tv-Livestream [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
'is_live': True,
'thumbnail': 're:^https?://.*\.jpg$'
},
'params': {
'skip_download': True,
}
}
def _real_extract(self, url):
display_id = 'live'
webpage = self._download_webpage(url, display_id)
title = self._live_title(self._og_search_title(webpage))
data_js = self._search_regex(
r'(?s)\nplaylist:\s*(\[.*?}\]),related:',
webpage, 'playlist configuration')
data_json = js_to_json(data_js)
data = json.loads(data_json)[0]
video_id = data['mediaid']
thumbnail = data.get('image')
formats = []
for format_num, s in enumerate(data['sources']):
ext = determine_ext(s['file'], None)
label_str = s.get('label')
if label_str is None:
label_str = '_%d' % format_num
if ext is None:
format_id = label_str
else:
format_id = '%s-%s' % (ext, label_str)
formats.append({
'url': s['file'],
'tbr': int_or_none(s.get('label')),
'ext': 'mp4',
'format_id': format_id,
'preference': -100 if '.smil' in s['file'] else 0,
})
self._sort_formats(formats)
return {
'id': video_id,
'display_id': display_id,
'title': title,
'formats': formats,
'is_live': True,
'thumbnail': thumbnail,
}

View File

@ -7,6 +7,7 @@ from .common import InfoExtractor
from ..utils import ( from ..utils import (
compat_urllib_parse, compat_urllib_parse,
ExtractorError, ExtractorError,
clean_html,
) )
@ -31,6 +32,11 @@ class NaverIE(InfoExtractor):
m_id = re.search(r'var rmcPlayer = new nhn.rmcnmv.RMCVideoPlayer\("(.+?)", "(.+?)"', m_id = re.search(r'var rmcPlayer = new nhn.rmcnmv.RMCVideoPlayer\("(.+?)", "(.+?)"',
webpage) webpage)
if m_id is None: if m_id is None:
m_error = re.search(
r'(?s)<div class="nation_error">\s*(?:<!--.*?-->)?\s*<p class="[^"]+">(?P<msg>.+?)</p>\s*</div>',
webpage)
if m_error:
raise ExtractorError(clean_html(m_error.group('msg')), expected=True)
raise ExtractorError('couldn\'t extract vid and key') raise ExtractorError('couldn\'t extract vid and key')
vid = m_id.group(1) vid = m_id.group(1)
key = m_id.group(2) key = m_id.group(2)

View File

@ -16,9 +16,9 @@ class NBCIE(InfoExtractor):
_TEST = { _TEST = {
'url': 'http://www.nbc.com/chicago-fire/video/i-am-a-firefighter/2734188', 'url': 'http://www.nbc.com/chicago-fire/video/i-am-a-firefighter/2734188',
'md5': '54d0fbc33e0b853a65d7b4de5c06d64e', # md5 checksum is not stable
'info_dict': { 'info_dict': {
'id': 'u1RInQZRN7QJ', 'id': 'bTmnLCvIbaaH',
'ext': 'flv', 'ext': 'flv',
'title': 'I Am a Firefighter', 'title': 'I Am a Firefighter',
'description': 'An emergency puts Dawson\'sf irefighter skills to the ultimate test in this four-part digital series.', 'description': 'An emergency puts Dawson\'sf irefighter skills to the ultimate test in this four-part digital series.',
@ -26,8 +26,7 @@ class NBCIE(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)
theplatform_url = self._search_regex('class="video-player video-player-full" data-mpx-url="(.*?)"', webpage, 'theplatform url') theplatform_url = self._search_regex('class="video-player video-player-full" data-mpx-url="(.*?)"', webpage, 'theplatform url')
if theplatform_url.startswith('//'): if theplatform_url.startswith('//'):
@ -57,7 +56,7 @@ class NBCNewsIE(InfoExtractor):
'md5': 'b2421750c9f260783721d898f4c42063', 'md5': 'b2421750c9f260783721d898f4c42063',
'info_dict': { 'info_dict': {
'id': 'I1wpAI_zmhsQ', 'id': 'I1wpAI_zmhsQ',
'ext': 'flv', 'ext': 'mp4',
'title': 'How Twitter Reacted To The Snowden Interview', 'title': 'How Twitter Reacted To The Snowden Interview',
'description': 'md5:65a0bd5d76fe114f3c2727aa3a81fe64', 'description': 'md5:65a0bd5d76fe114f3c2727aa3a81fe64',
}, },
@ -97,6 +96,8 @@ class NBCNewsIE(InfoExtractor):
] ]
for base_url in base_urls: for base_url in base_urls:
if not base_url:
continue
playlist_url = base_url + '?form=MPXNBCNewsAPI' playlist_url = base_url + '?form=MPXNBCNewsAPI'
all_videos = self._download_json(playlist_url, title)['videos'] all_videos = self._download_json(playlist_url, title)['videos']

View File

@ -18,16 +18,16 @@ class NDRIE(InfoExtractor):
_TESTS = [ _TESTS = [
{ {
'url': 'http://www.ndr.de/fernsehen/media/dienordreportage325.html', 'url': 'http://www.ndr.de/fernsehen/sendungen/nordmagazin/Kartoffeltage-in-der-Lewitz,nordmagazin25866.html',
'md5': '4a4eeafd17c3058b65f0c8f091355855', 'md5': '5bc5f5b92c82c0f8b26cddca34f8bb2c',
'note': 'Video file', 'note': 'Video file',
'info_dict': { 'info_dict': {
'id': '325', 'id': '25866',
'ext': 'mp4', 'ext': 'mp4',
'title': 'Blaue Bohnen aus Blocken', 'title': 'Kartoffeltage in der Lewitz',
'description': 'md5:190d71ba2ccddc805ed01547718963bc', 'description': 'md5:48c4c04dde604c8a9971b3d4e3b9eaa8',
'duration': 1715, 'duration': 166,
}, }
}, },
{ {
'url': 'http://www.ndr.de/info/audio51535.html', 'url': 'http://www.ndr.de/info/audio51535.html',

144
youtube_dl/extractor/nfl.py Normal file
View File

@ -0,0 +1,144 @@
# coding: utf-8
from __future__ import unicode_literals
import re
from .common import InfoExtractor
from ..utils import (
ExtractorError,
compat_urllib_parse_urlparse,
int_or_none,
remove_end,
)
class NFLIE(InfoExtractor):
IE_NAME = 'nfl.com'
_VALID_URL = r'''(?x)https?://
(?P<host>(?:www\.)?(?:nfl\.com|.*?\.clubs\.nfl\.com))/
(?:.+?/)*
(?P<id>(?:\d[a-z]{2}\d{13}|\w{8}\-(?:\w{4}\-){3}\w{12}))'''
_TESTS = [
{
'url': 'http://www.nfl.com/videos/nfl-game-highlights/0ap3000000398478/Week-3-Redskins-vs-Eagles-highlights',
'md5': '394ef771ddcd1354f665b471d78ec4c6',
'info_dict': {
'id': '0ap3000000398478',
'ext': 'mp4',
'title': 'Week 3: Redskins vs. Eagles highlights',
'description': 'md5:56323bfb0ac4ee5ab24bd05fdf3bf478',
'upload_date': '20140921',
'timestamp': 1411337580,
'thumbnail': 're:^https?://.*\.jpg$',
}
},
{
'url': 'http://prod.www.steelers.clubs.nfl.com/video-and-audio/videos/LIVE_Post_Game_vs_Browns/9d72f26a-9e2b-4718-84d3-09fb4046c266',
'md5': 'cf85bdb4bc49f6e9d3816d130c78279c',
'info_dict': {
'id': '9d72f26a-9e2b-4718-84d3-09fb4046c266',
'ext': 'mp4',
'title': 'LIVE: Post Game vs. Browns',
'description': 'md5:6a97f7e5ebeb4c0e69a418a89e0636e8',
'upload_date': '20131229',
'timestamp': 1388354455,
'thumbnail': 're:^https?://.*\.jpg$',
}
}
]
@staticmethod
def prepend_host(host, url):
if not url.startswith('http'):
if not url.startswith('/'):
url = '/%s' % url
url = 'http://{0:}{1:}'.format(host, url)
return url
@staticmethod
def format_from_stream(stream, protocol, host, path_prefix='',
preference=0, note=None):
url = '{protocol:}://{host:}/{prefix:}{path:}'.format(
protocol=protocol,
host=host,
prefix=path_prefix,
path=stream.get('path'),
)
return {
'url': url,
'vbr': int_or_none(stream.get('rate', 0), 1000),
'preference': preference,
'format_note': note,
}
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
video_id, host = mobj.group('id'), mobj.group('host')
webpage = self._download_webpage(url, video_id)
config_url = NFLIE.prepend_host(host, self._search_regex(
r'(?:config|configURL)\s*:\s*"([^"]+)"', webpage, 'config URL'))
config = self._download_json(config_url, video_id,
note='Downloading player config')
url_template = NFLIE.prepend_host(
host, '{contentURLTemplate:}'.format(**config))
video_data = self._download_json(
url_template.format(id=video_id), video_id)
formats = []
cdn_data = video_data.get('cdnData', {})
streams = cdn_data.get('bitrateInfo', [])
if cdn_data.get('format') == 'EXTERNAL_HTTP_STREAM':
parts = compat_urllib_parse_urlparse(cdn_data.get('uri'))
protocol, host = parts.scheme, parts.netloc
for stream in streams:
formats.append(
NFLIE.format_from_stream(stream, protocol, host))
else:
cdns = config.get('cdns')
if not cdns:
raise ExtractorError('Failed to get CDN data', expected=True)
for name, cdn in cdns.items():
# LimeLight streams don't seem to work
if cdn.get('name') == 'LIMELIGHT':
continue
protocol = cdn.get('protocol')
host = remove_end(cdn.get('host', ''), '/')
if not (protocol and host):
continue
prefix = cdn.get('pathprefix', '')
if prefix and not prefix.endswith('/'):
prefix = '%s/' % prefix
preference = 0
if protocol == 'rtmp':
preference = -2
elif 'prog' in name.lower():
preference = 1
for stream in streams:
formats.append(
NFLIE.format_from_stream(stream, protocol, host,
prefix, preference, name))
self._sort_formats(formats)
thumbnail = None
for q in ('xl', 'l', 'm', 's', 'xs'):
thumbnail = video_data.get('imagePaths', {}).get(q)
if thumbnail:
break
return {
'id': video_id,
'title': video_data.get('headline'),
'formats': formats,
'description': video_data.get('caption'),
'duration': video_data.get('duration'),
'thumbnail': thumbnail,
'timestamp': int_or_none(video_data.get('posted'), 1000),
}

Some files were not shown because too many files have changed in this diff Show More