mirror of
https://codeberg.org/polarisfm/youtube-dl
synced 2024-11-27 19:04:32 +01:00
commit
10724ec341
6
.github/ISSUE_TEMPLATE/1_broken_site.md
vendored
6
.github/ISSUE_TEMPLATE/1_broken_site.md
vendored
@ -18,7 +18,7 @@ title: ''
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dl:
|
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dl:
|
||||||
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.04.30. If it's not, see https://yt-dl.org/update on how to update. Issues with outdated version will be REJECTED.
|
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.07.30. If it's not, see https://yt-dl.org/update on how to update. Issues with outdated version will be REJECTED.
|
||||||
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
|
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
|
||||||
- Make sure that all URLs and arguments with special characters are properly quoted or escaped as explained in http://yt-dl.org/escape.
|
- Make sure that all URLs and arguments with special characters are properly quoted or escaped as explained in http://yt-dl.org/escape.
|
||||||
- Search the bugtracker for similar issues: http://yt-dl.org/search-issues. DO NOT post duplicates.
|
- Search the bugtracker for similar issues: http://yt-dl.org/search-issues. DO NOT post duplicates.
|
||||||
@ -26,7 +26,7 @@ Carefully read and work through this check list in order to prevent the most com
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
- [ ] I'm reporting a broken site support
|
- [ ] I'm reporting a broken site support
|
||||||
- [ ] I've verified that I'm running youtube-dl version **2019.04.30**
|
- [ ] I've verified that I'm running youtube-dl version **2019.07.30**
|
||||||
- [ ] I've checked that all provided URLs are alive and playable in a browser
|
- [ ] I've checked that all provided URLs are alive and playable in a browser
|
||||||
- [ ] I've checked that all URLs and arguments with special characters are properly quoted or escaped
|
- [ ] I've checked that all URLs and arguments with special characters are properly quoted or escaped
|
||||||
- [ ] I've searched the bugtracker for similar issues including closed ones
|
- [ ] I've searched the bugtracker for similar issues including closed ones
|
||||||
@ -41,7 +41,7 @@ Add the `-v` flag to your command line you run youtube-dl with (`youtube-dl -v <
|
|||||||
[debug] User config: []
|
[debug] User config: []
|
||||||
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
||||||
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
||||||
[debug] youtube-dl version 2019.04.30
|
[debug] youtube-dl version 2019.07.30
|
||||||
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
||||||
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
||||||
[debug] Proxy map: {}
|
[debug] Proxy map: {}
|
||||||
|
@ -19,7 +19,7 @@ labels: 'site-support-request'
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dl:
|
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dl:
|
||||||
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.04.30. If it's not, see https://yt-dl.org/update on how to update. Issues with outdated version will be REJECTED.
|
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.07.30. If it's not, see https://yt-dl.org/update on how to update. Issues with outdated version will be REJECTED.
|
||||||
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
|
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
|
||||||
- Make sure that site you are requesting is not dedicated to copyright infringement, see https://yt-dl.org/copyright-infringement. youtube-dl does not support such sites. In order for site support request to be accepted all provided example URLs should not violate any copyrights.
|
- Make sure that site you are requesting is not dedicated to copyright infringement, see https://yt-dl.org/copyright-infringement. youtube-dl does not support such sites. In order for site support request to be accepted all provided example URLs should not violate any copyrights.
|
||||||
- Search the bugtracker for similar site support requests: http://yt-dl.org/search-issues. DO NOT post duplicates.
|
- Search the bugtracker for similar site support requests: http://yt-dl.org/search-issues. DO NOT post duplicates.
|
||||||
@ -27,7 +27,7 @@ Carefully read and work through this check list in order to prevent the most com
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
- [ ] I'm reporting a new site support request
|
- [ ] I'm reporting a new site support request
|
||||||
- [ ] I've verified that I'm running youtube-dl version **2019.04.30**
|
- [ ] I've verified that I'm running youtube-dl version **2019.07.30**
|
||||||
- [ ] I've checked that all provided URLs are alive and playable in a browser
|
- [ ] I've checked that all provided URLs are alive and playable in a browser
|
||||||
- [ ] I've checked that none of provided URLs violate any copyrights
|
- [ ] I've checked that none of provided URLs violate any copyrights
|
||||||
- [ ] I've searched the bugtracker for similar site support requests including closed ones
|
- [ ] I've searched the bugtracker for similar site support requests including closed ones
|
||||||
|
@ -18,13 +18,13 @@ title: ''
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dl:
|
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dl:
|
||||||
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.04.30. If it's not, see https://yt-dl.org/update on how to update. Issues with outdated version will be REJECTED.
|
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.07.30. If it's not, see https://yt-dl.org/update on how to update. Issues with outdated version will be REJECTED.
|
||||||
- Search the bugtracker for similar site feature requests: http://yt-dl.org/search-issues. DO NOT post duplicates.
|
- Search the bugtracker for similar site feature requests: http://yt-dl.org/search-issues. DO NOT post duplicates.
|
||||||
- Finally, put x into all relevant boxes (like this [x])
|
- Finally, put x into all relevant boxes (like this [x])
|
||||||
-->
|
-->
|
||||||
|
|
||||||
- [ ] I'm reporting a site feature request
|
- [ ] I'm reporting a site feature request
|
||||||
- [ ] I've verified that I'm running youtube-dl version **2019.04.30**
|
- [ ] I've verified that I'm running youtube-dl version **2019.07.30**
|
||||||
- [ ] I've searched the bugtracker for similar site feature requests including closed ones
|
- [ ] I've searched the bugtracker for similar site feature requests including closed ones
|
||||||
|
|
||||||
|
|
||||||
|
6
.github/ISSUE_TEMPLATE/4_bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/4_bug_report.md
vendored
@ -18,7 +18,7 @@ title: ''
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dl:
|
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dl:
|
||||||
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.04.30. If it's not, see https://yt-dl.org/update on how to update. Issues with outdated version will be REJECTED.
|
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.07.30. If it's not, see https://yt-dl.org/update on how to update. Issues with outdated version will be REJECTED.
|
||||||
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
|
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
|
||||||
- Make sure that all URLs and arguments with special characters are properly quoted or escaped as explained in http://yt-dl.org/escape.
|
- Make sure that all URLs and arguments with special characters are properly quoted or escaped as explained in http://yt-dl.org/escape.
|
||||||
- Search the bugtracker for similar issues: http://yt-dl.org/search-issues. DO NOT post duplicates.
|
- Search the bugtracker for similar issues: http://yt-dl.org/search-issues. DO NOT post duplicates.
|
||||||
@ -27,7 +27,7 @@ Carefully read and work through this check list in order to prevent the most com
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
- [ ] I'm reporting a broken site support issue
|
- [ ] I'm reporting a broken site support issue
|
||||||
- [ ] I've verified that I'm running youtube-dl version **2019.04.30**
|
- [ ] I've verified that I'm running youtube-dl version **2019.07.30**
|
||||||
- [ ] I've checked that all provided URLs are alive and playable in a browser
|
- [ ] I've checked that all provided URLs are alive and playable in a browser
|
||||||
- [ ] I've checked that all URLs and arguments with special characters are properly quoted or escaped
|
- [ ] I've checked that all URLs and arguments with special characters are properly quoted or escaped
|
||||||
- [ ] I've searched the bugtracker for similar bug reports including closed ones
|
- [ ] I've searched the bugtracker for similar bug reports including closed ones
|
||||||
@ -43,7 +43,7 @@ Add the `-v` flag to your command line you run youtube-dl with (`youtube-dl -v <
|
|||||||
[debug] User config: []
|
[debug] User config: []
|
||||||
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
||||||
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
||||||
[debug] youtube-dl version 2019.04.30
|
[debug] youtube-dl version 2019.07.30
|
||||||
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
||||||
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
||||||
[debug] Proxy map: {}
|
[debug] Proxy map: {}
|
||||||
|
4
.github/ISSUE_TEMPLATE/5_feature_request.md
vendored
4
.github/ISSUE_TEMPLATE/5_feature_request.md
vendored
@ -19,13 +19,13 @@ labels: 'request'
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dl:
|
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dl:
|
||||||
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.04.30. If it's not, see https://yt-dl.org/update on how to update. Issues with outdated version will be REJECTED.
|
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.07.30. If it's not, see https://yt-dl.org/update on how to update. Issues with outdated version will be REJECTED.
|
||||||
- Search the bugtracker for similar feature requests: http://yt-dl.org/search-issues. DO NOT post duplicates.
|
- Search the bugtracker for similar feature requests: http://yt-dl.org/search-issues. DO NOT post duplicates.
|
||||||
- Finally, put x into all relevant boxes (like this [x])
|
- Finally, put x into all relevant boxes (like this [x])
|
||||||
-->
|
-->
|
||||||
|
|
||||||
- [ ] I'm reporting a feature request
|
- [ ] I'm reporting a feature request
|
||||||
- [ ] I've verified that I'm running youtube-dl version **2019.04.30**
|
- [ ] I've verified that I'm running youtube-dl version **2019.07.30**
|
||||||
- [ ] I've searched the bugtracker for similar feature requests including closed ones
|
- [ ] I've searched the bugtracker for similar feature requests including closed ones
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ python:
|
|||||||
- "3.6"
|
- "3.6"
|
||||||
- "pypy"
|
- "pypy"
|
||||||
- "pypy3"
|
- "pypy3"
|
||||||
|
dist: trusty
|
||||||
env:
|
env:
|
||||||
- YTDL_TEST_SET=core
|
- YTDL_TEST_SET=core
|
||||||
- YTDL_TEST_SET=download
|
- YTDL_TEST_SET=download
|
||||||
|
214
ChangeLog
214
ChangeLog
@ -1,3 +1,217 @@
|
|||||||
|
version 2019.07.30
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [youtube] Fix and improve title and description extraction (#21934)
|
||||||
|
|
||||||
|
|
||||||
|
version 2019.07.27
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [yahoo:japannews] Add support for yahoo.co.jp (#21698, #21265)
|
||||||
|
+ [discovery] Add support go.discovery.com URLs
|
||||||
|
* [youtube:playlist] Relax video regular expression (#21844)
|
||||||
|
* [generic] Restrict --default-search schemeless URLs detection pattern
|
||||||
|
(#21842)
|
||||||
|
* [vrv] Fix CMS signing query extraction (#21809)
|
||||||
|
|
||||||
|
|
||||||
|
version 2019.07.16
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [asiancrush] Add support for yuyutv.com, midnightpulp.com and cocoro.tv
|
||||||
|
(#21281, #21290)
|
||||||
|
* [kaltura] Check source format URL (#21290)
|
||||||
|
* [ctsnews] Fix YouTube embeds extraction (#21678)
|
||||||
|
+ [einthusan] Add support for einthusan.com (#21748, #21775)
|
||||||
|
+ [youtube] Add support for invidious.mastodon.host (#21777)
|
||||||
|
+ [gfycat] Extend URL regular expression (#21779, #21780)
|
||||||
|
* [youtube] Restrict is_live extraction (#21782)
|
||||||
|
|
||||||
|
|
||||||
|
version 2019.07.14
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [porn91] Fix extraction (#21312)
|
||||||
|
+ [yandexmusic] Extract track number and disk number (#21421)
|
||||||
|
+ [yandexmusic] Add support for multi disk albums (#21420, #21421)
|
||||||
|
* [lynda] Handle missing subtitles (#20490, #20513)
|
||||||
|
+ [youtube] Add more invidious instances to URL regular expression (#21694)
|
||||||
|
* [twitter] Improve uploader id extraction (#21705)
|
||||||
|
* [spankbang] Fix and improve metadata extraction
|
||||||
|
* [spankbang] Fix extraction (#21763, #21764)
|
||||||
|
+ [dlive] Add support for dlive.tv (#18080)
|
||||||
|
+ [livejournal] Add support for livejournal.com (#21526)
|
||||||
|
* [roosterteeth] Fix free episode extraction (#16094)
|
||||||
|
* [dbtv] Fix extraction
|
||||||
|
* [bellator] Fix extraction
|
||||||
|
- [rudo] Remove extractor (#18430, #18474)
|
||||||
|
* [facebook] Fallback to twitter:image meta for thumbnail extraction (#21224)
|
||||||
|
* [bleacherreport] Fix Bleacher Report CMS extraction
|
||||||
|
* [espn] Fix fivethirtyeight.com extraction
|
||||||
|
* [5tv] Relax video URL regular expression and support https URLs
|
||||||
|
* [youtube] Fix is_live extraction (#21734)
|
||||||
|
* [youtube] Fix authentication (#11270)
|
||||||
|
|
||||||
|
|
||||||
|
version 2019.07.12
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [adobepass] Add support for AT&T U-verse (mso ATT) (#13938, #21016)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [mgtv] Pass Referer HTTP header for format URLs (#21726)
|
||||||
|
+ [beeg] Add support for api/v6 v2 URLs without t argument (#21701)
|
||||||
|
* [voxmedia:volume] Improvevox embed extraction (#16846)
|
||||||
|
* [funnyordie] Move extraction to VoxMedia extractor (#16846)
|
||||||
|
* [gameinformer] Fix extraction (#8895, #15363, #17206)
|
||||||
|
* [funk] Fix extraction (#17915)
|
||||||
|
* [packtpub] Relax lesson URL regular expression (#21695)
|
||||||
|
* [packtpub] Fix extraction (#21268)
|
||||||
|
* [philharmoniedeparis] Relax URL regular expression (#21672)
|
||||||
|
* [peertube] Detect embed URLs in generic extraction (#21666)
|
||||||
|
* [mixer:vod] Relax URL regular expression (#21657, #21658)
|
||||||
|
+ [lecturio] Add support id based URLs (#21630)
|
||||||
|
+ [go] Add site info for disneynow (#21613)
|
||||||
|
* [ted] Restrict info regular expression (#21631)
|
||||||
|
* [twitch:vod] Actualize m3u8 URL (#21538, #21607)
|
||||||
|
* [vzaar] Fix videos with empty title (#21606)
|
||||||
|
* [tvland] Fix extraction (#21384)
|
||||||
|
* [arte] Clean extractor (#15583, #21614)
|
||||||
|
|
||||||
|
|
||||||
|
version 2019.07.02
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [utils] Introduce random_user_agent and use as default User-Agent (#21546)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [vevo] Add support for embed.vevo.com URLs (#21565)
|
||||||
|
+ [openload] Add support for oload.biz (#21574)
|
||||||
|
* [xiami] Update API base URL (#21575)
|
||||||
|
* [yourporn] Fix extraction (#21585)
|
||||||
|
+ [acast] Add support for URLs with episode id (#21444)
|
||||||
|
+ [dailymotion] Add support for DM.player embeds
|
||||||
|
* [soundcloud] Update client id
|
||||||
|
|
||||||
|
|
||||||
|
version 2019.06.27
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [go] Add support for disneynow.com (#21528)
|
||||||
|
* [mixer:vod] Relax URL regular expression (#21531, #21536)
|
||||||
|
* [drtv] Relax URL regular expression
|
||||||
|
* [fusion] Fix extraction (#17775, #21269)
|
||||||
|
- [nfb] Remove extractor (#21518)
|
||||||
|
+ [beeg] Add support for api/v6 v2 URLs (#21511)
|
||||||
|
+ [brightcove:new] Add support for playlists (#21331)
|
||||||
|
+ [openload] Add support for oload.life (#21495)
|
||||||
|
* [vimeo:channel,group] Make title extraction non fatal
|
||||||
|
* [vimeo:likes] Implement extrator in terms of channel extractor (#21493)
|
||||||
|
+ [pornhub] Add support for more paged video sources
|
||||||
|
+ [pornhub] Add support for downloading single pages and search pages (#15570)
|
||||||
|
* [pornhub] Rework extractors (#11922, #16078, #17454, #17936)
|
||||||
|
+ [youtube] Add another signature function pattern
|
||||||
|
* [tf1] Fix extraction (#21365, #21372)
|
||||||
|
* [crunchyroll] Move Accept-Language workaround to video extractor since
|
||||||
|
it causes playlists not to list any videos
|
||||||
|
* [crunchyroll:playlist] Fix and relax title extraction (#21291, #21443)
|
||||||
|
|
||||||
|
|
||||||
|
version 2019.06.21
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [utils] Restrict parse_codecs and add theora as known vcodec (#21381)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [youtube] Update signature function patterns (#21469, #21476)
|
||||||
|
* [youtube] Make --write-annotations non fatal (#21452)
|
||||||
|
+ [sixplay] Add support for rtlmost.hu (#21405)
|
||||||
|
* [youtube] Hardcode codec metadata for av01 video only formats (#21381)
|
||||||
|
* [toutv] Update client key (#21370)
|
||||||
|
+ [biqle] Add support for new embed domain
|
||||||
|
* [cbs] Improve DRM protected videos detection (#21339)
|
||||||
|
|
||||||
|
|
||||||
|
version 2019.06.08
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [downloader/common] Improve rate limit (#21301)
|
||||||
|
* [utils] Improve strip_or_none
|
||||||
|
* [extractor/common] Strip src attribute for HTML5 entries code (#18485,
|
||||||
|
#21169)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [ted] Fix playlist extraction (#20844, #21032)
|
||||||
|
* [vlive:playlist] Fix video extraction when no playlist is found (#20590)
|
||||||
|
+ [vlive] Add CH+ support (#16887, #21209)
|
||||||
|
+ [openload] Add support for oload.website (#21329)
|
||||||
|
+ [tvnow] Extract HD formats (#21201)
|
||||||
|
+ [redbulltv] Add support for rrn:content URLs (#21297)
|
||||||
|
* [youtube] Fix average rating extraction (#21304)
|
||||||
|
+ [bitchute] Extract HTML5 formats (#21306)
|
||||||
|
* [cbsnews] Fix extraction (#9659, #15397)
|
||||||
|
* [vvvvid] Relax URL regular expression (#21299)
|
||||||
|
+ [prosiebensat1] Add support for new API (#21272)
|
||||||
|
+ [vrv] Extract adaptive_hls formats (#21243)
|
||||||
|
* [viki] Switch to HTTPS (#21001)
|
||||||
|
* [LiveLeak] Check if the original videos exist (#21206, #21208)
|
||||||
|
* [rtp] Fix extraction (#15099)
|
||||||
|
* [youtube] Improve DRM protected videos detection (#1774)
|
||||||
|
+ [srgssrplay] Add support for popupvideoplayer URLs (#21155)
|
||||||
|
+ [24video] Add support for porno.24video.net (#21194)
|
||||||
|
+ [24video] Add support for 24video.site (#21193)
|
||||||
|
- [pornflip] Remove extractor
|
||||||
|
- [criterion] Remove extractor (#21195)
|
||||||
|
* [pornhub] Use HTTPS (#21061)
|
||||||
|
* [bitchute] Fix uploader extraction (#21076)
|
||||||
|
* [streamcloud] Reduce waiting time to 6 seconds (#21092)
|
||||||
|
- [novamov] Remove extractors (#21077)
|
||||||
|
+ [openload] Add support for oload.press (#21135)
|
||||||
|
* [vivo] Fix extraction (#18906, #19217)
|
||||||
|
|
||||||
|
|
||||||
|
version 2019.05.20
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [extractor/common] Move workaround for applying first Set-Cookie header
|
||||||
|
into a separate _apply_first_set_cookie_header method
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [safari] Fix authentication (#21090)
|
||||||
|
* [vk] Use _apply_first_set_cookie_header
|
||||||
|
* [vrt] Fix extraction (#20527)
|
||||||
|
+ [canvas] Add support for vrtnieuws and sporza site ids and extract
|
||||||
|
AES HLS formats
|
||||||
|
+ [vrv] Extract captions (#19238)
|
||||||
|
* [tele5] Improve video id extraction
|
||||||
|
* [tele5] Relax URL regular expression (#21020, #21063)
|
||||||
|
* [svtplay] Update API URL (#21075)
|
||||||
|
+ [yahoo:gyao] Add X-User-Agent header to dam proxy requests (#21071)
|
||||||
|
|
||||||
|
|
||||||
|
version 2019.05.11
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [utils] Transliterate "þ" as "th" (#20897)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [cloudflarestream] Add support for videodelivery.net (#21049)
|
||||||
|
+ [byutv] Add support for DVR videos (#20574, #20676)
|
||||||
|
+ [gfycat] Add support for URLs with tags (#20696, #20731)
|
||||||
|
+ [openload] Add support for verystream.com (#20701, #20967)
|
||||||
|
* [youtube] Use sp field value for signature field name (#18841, #18927,
|
||||||
|
#21028)
|
||||||
|
+ [yahoo:gyao] Extend URL regular expression (#21008)
|
||||||
|
* [youtube] Fix channel id extraction (#20982, #21003)
|
||||||
|
+ [sky] Add support for news.sky.com (#13055)
|
||||||
|
+ [youtube:entrylistbase] Retry on 5xx HTTP errors (#20965)
|
||||||
|
+ [francetvinfo] Extend video id extraction (#20619, #20740)
|
||||||
|
* [4tube] Update token hosts (#20918)
|
||||||
|
* [hotstar] Move to API v2 (#20931)
|
||||||
|
* [fox] Fix API error handling under python 2 (#20925)
|
||||||
|
+ [redbulltv] Extend URL regular expression (#20922)
|
||||||
|
|
||||||
|
|
||||||
version 2019.04.30
|
version 2019.04.30
|
||||||
|
|
||||||
Extractors
|
Extractors
|
||||||
|
@ -45,12 +45,12 @@ for test in gettestcases():
|
|||||||
|
|
||||||
RESULT = ('.' + domain + '\n' in LIST or '\n' + domain + '\n' in LIST)
|
RESULT = ('.' + domain + '\n' in LIST or '\n' + domain + '\n' in LIST)
|
||||||
|
|
||||||
if RESULT and ('info_dict' not in test or 'age_limit' not in test['info_dict'] or
|
if RESULT and ('info_dict' not in test or 'age_limit' not in test['info_dict']
|
||||||
test['info_dict']['age_limit'] != 18):
|
or test['info_dict']['age_limit'] != 18):
|
||||||
print('\nPotential missing age_limit check: {0}'.format(test['name']))
|
print('\nPotential missing age_limit check: {0}'.format(test['name']))
|
||||||
|
|
||||||
elif not RESULT and ('info_dict' in test and 'age_limit' in test['info_dict'] and
|
elif not RESULT and ('info_dict' in test and 'age_limit' in test['info_dict']
|
||||||
test['info_dict']['age_limit'] == 18):
|
and test['info_dict']['age_limit'] == 18):
|
||||||
print('\nPotential false negative: {0}'.format(test['name']))
|
print('\nPotential false negative: {0}'.format(test['name']))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -58,16 +58,8 @@
|
|||||||
- **ARD:mediathek**
|
- **ARD:mediathek**
|
||||||
- **ARDBetaMediathek**
|
- **ARDBetaMediathek**
|
||||||
- **Arkena**
|
- **Arkena**
|
||||||
- **arte.tv**
|
|
||||||
- **arte.tv:+7**
|
- **arte.tv:+7**
|
||||||
- **arte.tv:cinema**
|
|
||||||
- **arte.tv:concert**
|
|
||||||
- **arte.tv:creative**
|
|
||||||
- **arte.tv:ddc**
|
|
||||||
- **arte.tv:embed**
|
- **arte.tv:embed**
|
||||||
- **arte.tv:future**
|
|
||||||
- **arte.tv:info**
|
|
||||||
- **arte.tv:magazine**
|
|
||||||
- **arte.tv:playlist**
|
- **arte.tv:playlist**
|
||||||
- **AsianCrush**
|
- **AsianCrush**
|
||||||
- **AsianCrushPlaylist**
|
- **AsianCrushPlaylist**
|
||||||
@ -78,7 +70,6 @@
|
|||||||
- **AudioBoom**
|
- **AudioBoom**
|
||||||
- **audiomack**
|
- **audiomack**
|
||||||
- **audiomack:album**
|
- **audiomack:album**
|
||||||
- **auroravid**: AuroraVid
|
|
||||||
- **AWAAN**
|
- **AWAAN**
|
||||||
- **awaan:live**
|
- **awaan:live**
|
||||||
- **awaan:season**
|
- **awaan:season**
|
||||||
@ -150,6 +141,7 @@
|
|||||||
- **CBSInteractive**
|
- **CBSInteractive**
|
||||||
- **CBSLocal**
|
- **CBSLocal**
|
||||||
- **cbsnews**: CBS News
|
- **cbsnews**: CBS News
|
||||||
|
- **cbsnews:embed**
|
||||||
- **cbsnews:livevideo**: CBS News Live Videos
|
- **cbsnews:livevideo**: CBS News Live Videos
|
||||||
- **CBSSports**
|
- **CBSSports**
|
||||||
- **CCMA**
|
- **CCMA**
|
||||||
@ -174,7 +166,6 @@
|
|||||||
- **Clipsyndicate**
|
- **Clipsyndicate**
|
||||||
- **CloserToTruth**
|
- **CloserToTruth**
|
||||||
- **CloudflareStream**
|
- **CloudflareStream**
|
||||||
- **cloudtime**: CloudTime
|
|
||||||
- **Cloudy**
|
- **Cloudy**
|
||||||
- **Clubic**
|
- **Clubic**
|
||||||
- **Clyp**
|
- **Clyp**
|
||||||
@ -194,7 +185,6 @@
|
|||||||
- **Coub**
|
- **Coub**
|
||||||
- **Cracked**
|
- **Cracked**
|
||||||
- **Crackle**
|
- **Crackle**
|
||||||
- **Criterion**
|
|
||||||
- **CrooksAndLiars**
|
- **CrooksAndLiars**
|
||||||
- **crunchyroll**
|
- **crunchyroll**
|
||||||
- **crunchyroll:playlist**
|
- **crunchyroll:playlist**
|
||||||
@ -233,6 +223,8 @@
|
|||||||
- **DiscoveryNetworksDe**
|
- **DiscoveryNetworksDe**
|
||||||
- **DiscoveryVR**
|
- **DiscoveryVR**
|
||||||
- **Disney**
|
- **Disney**
|
||||||
|
- **dlive:stream**
|
||||||
|
- **dlive:vod**
|
||||||
- **Dotsub**
|
- **Dotsub**
|
||||||
- **DouyuShow**
|
- **DouyuShow**
|
||||||
- **DouyuTV**: 斗鱼
|
- **DouyuTV**: 斗鱼
|
||||||
@ -315,9 +307,7 @@
|
|||||||
- **FrontendMastersCourse**
|
- **FrontendMastersCourse**
|
||||||
- **FrontendMastersLesson**
|
- **FrontendMastersLesson**
|
||||||
- **Funimation**
|
- **Funimation**
|
||||||
- **FunkChannel**
|
- **Funk**
|
||||||
- **FunkMix**
|
|
||||||
- **FunnyOrDie**
|
|
||||||
- **Fusion**
|
- **Fusion**
|
||||||
- **Fux**
|
- **Fux**
|
||||||
- **FXNetworks**
|
- **FXNetworks**
|
||||||
@ -460,6 +450,7 @@
|
|||||||
- **linkedin:learning:course**
|
- **linkedin:learning:course**
|
||||||
- **LinuxAcademy**
|
- **LinuxAcademy**
|
||||||
- **LiTV**
|
- **LiTV**
|
||||||
|
- **LiveJournal**
|
||||||
- **LiveLeak**
|
- **LiveLeak**
|
||||||
- **LiveLeakEmbed**
|
- **LiveLeakEmbed**
|
||||||
- **livestream**
|
- **livestream**
|
||||||
@ -583,7 +574,6 @@
|
|||||||
- **NextTV**: 壹電視
|
- **NextTV**: 壹電視
|
||||||
- **Nexx**
|
- **Nexx**
|
||||||
- **NexxEmbed**
|
- **NexxEmbed**
|
||||||
- **nfb**: National Film Board of Canada
|
|
||||||
- **nfl.com**
|
- **nfl.com**
|
||||||
- **NhkVod**
|
- **NhkVod**
|
||||||
- **nhl.com**
|
- **nhl.com**
|
||||||
@ -609,7 +599,6 @@
|
|||||||
- **nowness**
|
- **nowness**
|
||||||
- **nowness:playlist**
|
- **nowness:playlist**
|
||||||
- **nowness:series**
|
- **nowness:series**
|
||||||
- **nowvideo**: NowVideo
|
|
||||||
- **Noz**
|
- **Noz**
|
||||||
- **npo**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
- **npo**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
||||||
- **npo.nl:live**
|
- **npo.nl:live**
|
||||||
@ -693,11 +682,11 @@
|
|||||||
- **PopcornTV**
|
- **PopcornTV**
|
||||||
- **PornCom**
|
- **PornCom**
|
||||||
- **PornerBros**
|
- **PornerBros**
|
||||||
- **PornFlip**
|
|
||||||
- **PornHd**
|
- **PornHd**
|
||||||
- **PornHub**: PornHub and Thumbzilla
|
- **PornHub**: PornHub and Thumbzilla
|
||||||
- **PornHubPlaylist**
|
- **PornHubPagedVideoList**
|
||||||
- **PornHubUserVideos**
|
- **PornHubUser**
|
||||||
|
- **PornHubUserVideosUpload**
|
||||||
- **Pornotube**
|
- **Pornotube**
|
||||||
- **PornoVoisines**
|
- **PornoVoisines**
|
||||||
- **PornoXO**
|
- **PornoXO**
|
||||||
@ -734,6 +723,7 @@
|
|||||||
- **RBMARadio**
|
- **RBMARadio**
|
||||||
- **RDS**: RDS.ca
|
- **RDS**: RDS.ca
|
||||||
- **RedBullTV**
|
- **RedBullTV**
|
||||||
|
- **RedBullTVRrnContent**
|
||||||
- **Reddit**
|
- **Reddit**
|
||||||
- **RedditR**
|
- **RedditR**
|
||||||
- **RedTube**
|
- **RedTube**
|
||||||
@ -767,7 +757,6 @@
|
|||||||
- **rtve.es:television**
|
- **rtve.es:television**
|
||||||
- **RTVNH**
|
- **RTVNH**
|
||||||
- **RTVS**
|
- **RTVS**
|
||||||
- **Rudo**
|
|
||||||
- **RUHD**
|
- **RUHD**
|
||||||
- **rutube**: Rutube videos
|
- **rutube**: Rutube videos
|
||||||
- **rutube:channel**: Rutube channels
|
- **rutube:channel**: Rutube channels
|
||||||
@ -805,6 +794,7 @@
|
|||||||
- **ShowRoomLive**
|
- **ShowRoomLive**
|
||||||
- **Sina**
|
- **Sina**
|
||||||
- **SkylineWebcams**
|
- **SkylineWebcams**
|
||||||
|
- **SkyNews**
|
||||||
- **skynewsarabia:article**
|
- **skynewsarabia:article**
|
||||||
- **skynewsarabia:video**
|
- **skynewsarabia:video**
|
||||||
- **SkySports**
|
- **SkySports**
|
||||||
@ -898,7 +888,6 @@
|
|||||||
- **TF1**
|
- **TF1**
|
||||||
- **TFO**
|
- **TFO**
|
||||||
- **TheIntercept**
|
- **TheIntercept**
|
||||||
- **theoperaplatform**
|
|
||||||
- **ThePlatform**
|
- **ThePlatform**
|
||||||
- **ThePlatformFeed**
|
- **ThePlatformFeed**
|
||||||
- **TheScene**
|
- **TheScene**
|
||||||
@ -999,6 +988,7 @@
|
|||||||
- **Vbox7**
|
- **Vbox7**
|
||||||
- **VeeHD**
|
- **VeeHD**
|
||||||
- **Veoh**
|
- **Veoh**
|
||||||
|
- **verystream**
|
||||||
- **Vessel**
|
- **Vessel**
|
||||||
- **Vesti**: Вести.Ru
|
- **Vesti**: Вести.Ru
|
||||||
- **Vevo**
|
- **Vevo**
|
||||||
@ -1022,7 +1012,6 @@
|
|||||||
- **videomore:video**
|
- **videomore:video**
|
||||||
- **VideoPremium**
|
- **VideoPremium**
|
||||||
- **VideoPress**
|
- **VideoPress**
|
||||||
- **videoweed**: VideoWeed
|
|
||||||
- **Vidio**
|
- **Vidio**
|
||||||
- **VidLii**
|
- **VidLii**
|
||||||
- **vidme**
|
- **vidme**
|
||||||
@ -1069,7 +1058,7 @@
|
|||||||
- **VoxMediaVolume**
|
- **VoxMediaVolume**
|
||||||
- **vpro**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
- **vpro**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
||||||
- **Vrak**
|
- **Vrak**
|
||||||
- **VRT**: deredactie.be, sporza.be, cobra.be and cobra.canvas.be
|
- **VRT**: VRT NWS, Flanders News, Flandern Info and Sporza
|
||||||
- **VrtNU**: VrtNU.be
|
- **VrtNU**: VrtNU.be
|
||||||
- **vrv**
|
- **vrv**
|
||||||
- **vrv:series**
|
- **vrv:series**
|
||||||
@ -1099,7 +1088,6 @@
|
|||||||
- **Weibo**
|
- **Weibo**
|
||||||
- **WeiboMobile**
|
- **WeiboMobile**
|
||||||
- **WeiqiTV**: WQTV
|
- **WeiqiTV**: WQTV
|
||||||
- **wholecloud**: WholeCloud
|
|
||||||
- **Wimp**
|
- **Wimp**
|
||||||
- **Wistia**
|
- **Wistia**
|
||||||
- **wnl**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
- **wnl**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
||||||
@ -1129,6 +1117,7 @@
|
|||||||
- **Yahoo**: Yahoo screen and movies
|
- **Yahoo**: Yahoo screen and movies
|
||||||
- **yahoo:gyao**
|
- **yahoo:gyao**
|
||||||
- **yahoo:gyao:player**
|
- **yahoo:gyao:player**
|
||||||
|
- **yahoo:japannews**: Yahoo! Japan News
|
||||||
- **YandexDisk**
|
- **YandexDisk**
|
||||||
- **yandexmusic:album**: Яндекс.Музыка - Альбом
|
- **yandexmusic:album**: Яндекс.Музыка - Альбом
|
||||||
- **yandexmusic:playlist**: Яндекс.Музыка - Плейлист
|
- **yandexmusic:playlist**: Яндекс.Музыка - Плейлист
|
||||||
|
@ -3,4 +3,4 @@ universal = True
|
|||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
exclude = youtube_dl/extractor/__init__.py,devscripts/buildserver.py,devscripts/lazy_load_template.py,devscripts/make_issue_template.py,setup.py,build,.git,venv
|
exclude = youtube_dl/extractor/__init__.py,devscripts/buildserver.py,devscripts/lazy_load_template.py,devscripts/make_issue_template.py,setup.py,build,.git,venv
|
||||||
ignore = E402,E501,E731,E741
|
ignore = E402,E501,E731,E741,W503
|
||||||
|
@ -44,16 +44,16 @@ class TestAES(unittest.TestCase):
|
|||||||
def test_decrypt_text(self):
|
def test_decrypt_text(self):
|
||||||
password = intlist_to_bytes(self.key).decode('utf-8')
|
password = intlist_to_bytes(self.key).decode('utf-8')
|
||||||
encrypted = base64.b64encode(
|
encrypted = base64.b64encode(
|
||||||
intlist_to_bytes(self.iv[:8]) +
|
intlist_to_bytes(self.iv[:8])
|
||||||
b'\x17\x15\x93\xab\x8d\x80V\xcdV\xe0\t\xcdo\xc2\xa5\xd8ksM\r\xe27N\xae'
|
+ b'\x17\x15\x93\xab\x8d\x80V\xcdV\xe0\t\xcdo\xc2\xa5\xd8ksM\r\xe27N\xae'
|
||||||
).decode('utf-8')
|
).decode('utf-8')
|
||||||
decrypted = (aes_decrypt_text(encrypted, password, 16))
|
decrypted = (aes_decrypt_text(encrypted, password, 16))
|
||||||
self.assertEqual(decrypted, self.secret_msg)
|
self.assertEqual(decrypted, self.secret_msg)
|
||||||
|
|
||||||
password = intlist_to_bytes(self.key).decode('utf-8')
|
password = intlist_to_bytes(self.key).decode('utf-8')
|
||||||
encrypted = base64.b64encode(
|
encrypted = base64.b64encode(
|
||||||
intlist_to_bytes(self.iv[:8]) +
|
intlist_to_bytes(self.iv[:8])
|
||||||
b'\x0b\xe6\xa4\xd9z\x0e\xb8\xb9\xd0\xd4i_\x85\x1d\x99\x98_\xe5\x80\xe7.\xbf\xa5\x83'
|
+ b'\x0b\xe6\xa4\xd9z\x0e\xb8\xb9\xd0\xd4i_\x85\x1d\x99\x98_\xe5\x80\xe7.\xbf\xa5\x83'
|
||||||
).decode('utf-8')
|
).decode('utf-8')
|
||||||
decrypted = (aes_decrypt_text(encrypted, password, 32))
|
decrypted = (aes_decrypt_text(encrypted, password, 32))
|
||||||
self.assertEqual(decrypted, self.secret_msg)
|
self.assertEqual(decrypted, self.secret_msg)
|
||||||
|
@ -34,8 +34,8 @@ def _make_testfunc(testfile):
|
|||||||
def test_func(self):
|
def test_func(self):
|
||||||
as_file = os.path.join(TEST_DIR, testfile)
|
as_file = os.path.join(TEST_DIR, testfile)
|
||||||
swf_file = os.path.join(TEST_DIR, test_id + '.swf')
|
swf_file = os.path.join(TEST_DIR, test_id + '.swf')
|
||||||
if ((not os.path.exists(swf_file)) or
|
if ((not os.path.exists(swf_file))
|
||||||
os.path.getmtime(swf_file) < os.path.getmtime(as_file)):
|
or os.path.getmtime(swf_file) < os.path.getmtime(as_file)):
|
||||||
# Recompile
|
# Recompile
|
||||||
try:
|
try:
|
||||||
subprocess.check_call([
|
subprocess.check_call([
|
||||||
|
@ -73,6 +73,7 @@ from youtube_dl.utils import (
|
|||||||
smuggle_url,
|
smuggle_url,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
strip_jsonp,
|
strip_jsonp,
|
||||||
|
strip_or_none,
|
||||||
timeconvert,
|
timeconvert,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
@ -183,7 +184,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(sanitize_filename(
|
self.assertEqual(sanitize_filename(
|
||||||
'ÂÃÄÀÁÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖŐØŒÙÚÛÜŰÝÞßàáâãäåæçèéêëìíîïðñòóôõöőøœùúûüűýþÿ', restricted=True),
|
'ÂÃÄÀÁÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖŐØŒÙÚÛÜŰÝÞßàáâãäåæçèéêëìíîïðñòóôõöőøœùúûüűýþÿ', restricted=True),
|
||||||
'AAAAAAAECEEEEIIIIDNOOOOOOOOEUUUUUYPssaaaaaaaeceeeeiiiionooooooooeuuuuuypy')
|
'AAAAAAAECEEEEIIIIDNOOOOOOOOEUUUUUYTHssaaaaaaaeceeeeiiiionooooooooeuuuuuythy')
|
||||||
|
|
||||||
def test_sanitize_ids(self):
|
def test_sanitize_ids(self):
|
||||||
self.assertEqual(sanitize_filename('_n_cd26wFpw', is_id=True), '_n_cd26wFpw')
|
self.assertEqual(sanitize_filename('_n_cd26wFpw', is_id=True), '_n_cd26wFpw')
|
||||||
@ -752,6 +753,18 @@ class TestUtil(unittest.TestCase):
|
|||||||
d = json.loads(stripped)
|
d = json.loads(stripped)
|
||||||
self.assertEqual(d, {'status': 'success'})
|
self.assertEqual(d, {'status': 'success'})
|
||||||
|
|
||||||
|
def test_strip_or_none(self):
|
||||||
|
self.assertEqual(strip_or_none(' abc'), 'abc')
|
||||||
|
self.assertEqual(strip_or_none('abc '), 'abc')
|
||||||
|
self.assertEqual(strip_or_none(' abc '), 'abc')
|
||||||
|
self.assertEqual(strip_or_none('\tabc\t'), 'abc')
|
||||||
|
self.assertEqual(strip_or_none('\n\tabc\n\t'), 'abc')
|
||||||
|
self.assertEqual(strip_or_none('abc'), 'abc')
|
||||||
|
self.assertEqual(strip_or_none(''), '')
|
||||||
|
self.assertEqual(strip_or_none(None), None)
|
||||||
|
self.assertEqual(strip_or_none(42), None)
|
||||||
|
self.assertEqual(strip_or_none([]), None)
|
||||||
|
|
||||||
def test_uppercase_escape(self):
|
def test_uppercase_escape(self):
|
||||||
self.assertEqual(uppercase_escape('aä'), 'aä')
|
self.assertEqual(uppercase_escape('aä'), 'aä')
|
||||||
self.assertEqual(uppercase_escape('\\U0001d550'), '𝕐')
|
self.assertEqual(uppercase_escape('\\U0001d550'), '𝕐')
|
||||||
@ -809,6 +822,15 @@ class TestUtil(unittest.TestCase):
|
|||||||
'vcodec': 'av01.0.05M.08',
|
'vcodec': 'av01.0.05M.08',
|
||||||
'acodec': 'none',
|
'acodec': 'none',
|
||||||
})
|
})
|
||||||
|
self.assertEqual(parse_codecs('theora, vorbis'), {
|
||||||
|
'vcodec': 'theora',
|
||||||
|
'acodec': 'vorbis',
|
||||||
|
})
|
||||||
|
self.assertEqual(parse_codecs('unknownvcodec, unknownacodec'), {
|
||||||
|
'vcodec': 'unknownvcodec',
|
||||||
|
'acodec': 'unknownacodec',
|
||||||
|
})
|
||||||
|
self.assertEqual(parse_codecs('unknown'), {})
|
||||||
|
|
||||||
def test_escape_rfc3986(self):
|
def test_escape_rfc3986(self):
|
||||||
reserved = "!*'();:@&=+$,/?#[]"
|
reserved = "!*'();:@&=+$,/?#[]"
|
||||||
|
@ -400,9 +400,9 @@ class YoutubeDL(object):
|
|||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if (sys.platform != 'win32' and
|
if (sys.platform != 'win32'
|
||||||
sys.getfilesystemencoding() in ['ascii', 'ANSI_X3.4-1968'] and
|
and sys.getfilesystemencoding() in ['ascii', 'ANSI_X3.4-1968']
|
||||||
not params.get('restrictfilenames', False)):
|
and not params.get('restrictfilenames', False)):
|
||||||
# Unicode filesystem API will throw errors (#1474, #13027)
|
# Unicode filesystem API will throw errors (#1474, #13027)
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
'Assuming --restrict-filenames since file system encoding '
|
'Assuming --restrict-filenames since file system encoding '
|
||||||
@ -440,9 +440,9 @@ class YoutubeDL(object):
|
|||||||
if re.match(r'^-[0-9A-Za-z_-]{10}$', a)]
|
if re.match(r'^-[0-9A-Za-z_-]{10}$', a)]
|
||||||
if idxs:
|
if idxs:
|
||||||
correct_argv = (
|
correct_argv = (
|
||||||
['youtube-dl'] +
|
['youtube-dl']
|
||||||
[a for i, a in enumerate(argv) if i not in idxs] +
|
+ [a for i, a in enumerate(argv) if i not in idxs]
|
||||||
['--'] + [argv[i] for i in idxs]
|
+ ['--'] + [argv[i] for i in idxs]
|
||||||
)
|
)
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
'Long argument string detected. '
|
'Long argument string detected. '
|
||||||
@ -850,8 +850,8 @@ class YoutubeDL(object):
|
|||||||
if result_type in ('url', 'url_transparent'):
|
if result_type in ('url', 'url_transparent'):
|
||||||
ie_result['url'] = sanitize_url(ie_result['url'])
|
ie_result['url'] = sanitize_url(ie_result['url'])
|
||||||
extract_flat = self.params.get('extract_flat', False)
|
extract_flat = self.params.get('extract_flat', False)
|
||||||
if ((extract_flat == 'in_playlist' and 'playlist' in extra_info) or
|
if ((extract_flat == 'in_playlist' and 'playlist' in extra_info)
|
||||||
extract_flat is True):
|
or extract_flat is True):
|
||||||
if self.params.get('forcejson', False):
|
if self.params.get('forcejson', False):
|
||||||
self.to_stdout(json.dumps(ie_result))
|
self.to_stdout(json.dumps(ie_result))
|
||||||
return ie_result
|
return ie_result
|
||||||
@ -1619,9 +1619,9 @@ class YoutubeDL(object):
|
|||||||
# https://github.com/ytdl-org/youtube-dl/issues/10083).
|
# https://github.com/ytdl-org/youtube-dl/issues/10083).
|
||||||
incomplete_formats = (
|
incomplete_formats = (
|
||||||
# All formats are video-only or
|
# All formats are video-only or
|
||||||
all(f.get('vcodec') != 'none' and f.get('acodec') == 'none' for f in formats) or
|
all(f.get('vcodec') != 'none' and f.get('acodec') == 'none' for f in formats)
|
||||||
# all formats are audio-only
|
# all formats are audio-only
|
||||||
all(f.get('vcodec') == 'none' and f.get('acodec') != 'none' for f in formats))
|
or all(f.get('vcodec') == 'none' and f.get('acodec') != 'none' for f in formats))
|
||||||
|
|
||||||
ctx = {
|
ctx = {
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
@ -1947,8 +1947,8 @@ class YoutubeDL(object):
|
|||||||
else:
|
else:
|
||||||
assert fixup_policy in ('ignore', 'never')
|
assert fixup_policy in ('ignore', 'never')
|
||||||
|
|
||||||
if (info_dict.get('requested_formats') is None and
|
if (info_dict.get('requested_formats') is None
|
||||||
info_dict.get('container') == 'm4a_dash'):
|
and info_dict.get('container') == 'm4a_dash'):
|
||||||
if fixup_policy == 'warn':
|
if fixup_policy == 'warn':
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
'%s: writing DASH m4a. '
|
'%s: writing DASH m4a. '
|
||||||
@ -1967,9 +1967,9 @@ class YoutubeDL(object):
|
|||||||
else:
|
else:
|
||||||
assert fixup_policy in ('ignore', 'never')
|
assert fixup_policy in ('ignore', 'never')
|
||||||
|
|
||||||
if (info_dict.get('protocol') == 'm3u8_native' or
|
if (info_dict.get('protocol') == 'm3u8_native'
|
||||||
info_dict.get('protocol') == 'm3u8' and
|
or info_dict.get('protocol') == 'm3u8'
|
||||||
self.params.get('hls_prefer_native')):
|
and self.params.get('hls_prefer_native')):
|
||||||
if fixup_policy == 'warn':
|
if fixup_policy == 'warn':
|
||||||
self.report_warning('%s: malformed AAC bitstream detected.' % (
|
self.report_warning('%s: malformed AAC bitstream detected.' % (
|
||||||
info_dict['id']))
|
info_dict['id']))
|
||||||
@ -1995,10 +1995,10 @@ class YoutubeDL(object):
|
|||||||
def download(self, url_list):
|
def download(self, url_list):
|
||||||
"""Download a given list of URLs."""
|
"""Download a given list of URLs."""
|
||||||
outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
|
outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
|
||||||
if (len(url_list) > 1 and
|
if (len(url_list) > 1
|
||||||
outtmpl != '-' and
|
and outtmpl != '-'
|
||||||
'%' not in outtmpl and
|
and '%' not in outtmpl
|
||||||
self.params.get('max_downloads') != 1):
|
and self.params.get('max_downloads') != 1):
|
||||||
raise SameFileError(outtmpl)
|
raise SameFileError(outtmpl)
|
||||||
|
|
||||||
for url in url_list:
|
for url in url_list:
|
||||||
@ -2143,8 +2143,8 @@ class YoutubeDL(object):
|
|||||||
if res:
|
if res:
|
||||||
res += ', '
|
res += ', '
|
||||||
res += '%s container' % fdict['container']
|
res += '%s container' % fdict['container']
|
||||||
if (fdict.get('vcodec') is not None and
|
if (fdict.get('vcodec') is not None
|
||||||
fdict.get('vcodec') != 'none'):
|
and fdict.get('vcodec') != 'none'):
|
||||||
if res:
|
if res:
|
||||||
res += ', '
|
res += ', '
|
||||||
res += fdict['vcodec']
|
res += fdict['vcodec']
|
||||||
|
@ -94,7 +94,7 @@ def _real_main(argv=None):
|
|||||||
if opts.verbose:
|
if opts.verbose:
|
||||||
write_string('[debug] Batch file urls: ' + repr(batch_urls) + '\n')
|
write_string('[debug] Batch file urls: ' + repr(batch_urls) + '\n')
|
||||||
except IOError:
|
except IOError:
|
||||||
sys.exit('ERROR: batch file could not be read')
|
sys.exit('ERROR: batch file %s could not be read' % opts.batchfile)
|
||||||
all_urls = batch_urls + [url.strip() for url in args] # batch_urls are already striped in read_batch_urls
|
all_urls = batch_urls + [url.strip() for url in args] # batch_urls are already striped in read_batch_urls
|
||||||
_enc = preferredencoding()
|
_enc = preferredencoding()
|
||||||
all_urls = [url.decode(_enc, 'ignore') if isinstance(url, bytes) else url for url in all_urls]
|
all_urls = [url.decode(_enc, 'ignore') if isinstance(url, bytes) else url for url in all_urls]
|
||||||
@ -230,14 +230,14 @@ def _real_main(argv=None):
|
|||||||
if opts.allsubtitles and not opts.writeautomaticsub:
|
if opts.allsubtitles and not opts.writeautomaticsub:
|
||||||
opts.writesubtitles = True
|
opts.writesubtitles = True
|
||||||
|
|
||||||
outtmpl = ((opts.outtmpl is not None and opts.outtmpl) or
|
outtmpl = ((opts.outtmpl is not None and opts.outtmpl)
|
||||||
(opts.format == '-1' and opts.usetitle and '%(title)s-%(id)s-%(format)s.%(ext)s') or
|
or (opts.format == '-1' and opts.usetitle and '%(title)s-%(id)s-%(format)s.%(ext)s')
|
||||||
(opts.format == '-1' and '%(id)s-%(format)s.%(ext)s') or
|
or (opts.format == '-1' and '%(id)s-%(format)s.%(ext)s')
|
||||||
(opts.usetitle and opts.autonumber and '%(autonumber)s-%(title)s-%(id)s.%(ext)s') or
|
or (opts.usetitle and opts.autonumber and '%(autonumber)s-%(title)s-%(id)s.%(ext)s')
|
||||||
(opts.usetitle and '%(title)s-%(id)s.%(ext)s') or
|
or (opts.usetitle and '%(title)s-%(id)s.%(ext)s')
|
||||||
(opts.useid and '%(id)s.%(ext)s') or
|
or (opts.useid and '%(id)s.%(ext)s')
|
||||||
(opts.autonumber and '%(autonumber)s-%(id)s.%(ext)s') or
|
or (opts.autonumber and '%(autonumber)s-%(id)s.%(ext)s')
|
||||||
DEFAULT_OUTTMPL)
|
or DEFAULT_OUTTMPL)
|
||||||
if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
|
if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
|
||||||
parser.error('Cannot download a video and extract audio into the same'
|
parser.error('Cannot download a video and extract audio into the same'
|
||||||
' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
|
' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
|
||||||
|
@ -2649,9 +2649,9 @@ else:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
args = shlex.split('中文')
|
args = shlex.split('中文')
|
||||||
assert (isinstance(args, list) and
|
assert (isinstance(args, list)
|
||||||
isinstance(args[0], compat_str) and
|
and isinstance(args[0], compat_str)
|
||||||
args[0] == '中文')
|
and args[0] == '中文')
|
||||||
compat_shlex_split = shlex.split
|
compat_shlex_split = shlex.split
|
||||||
except (AssertionError, UnicodeEncodeError):
|
except (AssertionError, UnicodeEncodeError):
|
||||||
# Working around shlex issue with unicode strings on some python 2
|
# Working around shlex issue with unicode strings on some python 2
|
||||||
|
@ -176,7 +176,9 @@ class FileDownloader(object):
|
|||||||
return
|
return
|
||||||
speed = float(byte_counter) / elapsed
|
speed = float(byte_counter) / elapsed
|
||||||
if speed > rate_limit:
|
if speed > rate_limit:
|
||||||
time.sleep(max((byte_counter // rate_limit) - elapsed, 0))
|
sleep_time = float(byte_counter) / rate_limit - elapsed
|
||||||
|
if sleep_time > 0:
|
||||||
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
def temp_name(self, filename):
|
def temp_name(self, filename):
|
||||||
"""Returns a temporary filename for the given filename."""
|
"""Returns a temporary filename for the given filename."""
|
||||||
@ -330,15 +332,15 @@ class FileDownloader(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
nooverwrites_and_exists = (
|
nooverwrites_and_exists = (
|
||||||
self.params.get('nooverwrites', False) and
|
self.params.get('nooverwrites', False)
|
||||||
os.path.exists(encodeFilename(filename))
|
and os.path.exists(encodeFilename(filename))
|
||||||
)
|
)
|
||||||
|
|
||||||
if not hasattr(filename, 'write'):
|
if not hasattr(filename, 'write'):
|
||||||
continuedl_and_exists = (
|
continuedl_and_exists = (
|
||||||
self.params.get('continuedl', True) and
|
self.params.get('continuedl', True)
|
||||||
os.path.isfile(encodeFilename(filename)) and
|
and os.path.isfile(encodeFilename(filename))
|
||||||
not self.params.get('nopart', False)
|
and not self.params.get('nopart', False)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check file already present
|
# Check file already present
|
||||||
|
@ -53,7 +53,7 @@ class DashSegmentsFD(FragmentFD):
|
|||||||
except compat_urllib_error.HTTPError as err:
|
except compat_urllib_error.HTTPError as err:
|
||||||
# YouTube may often return 404 HTTP error for a fragment causing the
|
# YouTube may often return 404 HTTP error for a fragment causing the
|
||||||
# whole download to fail. However if the same fragment is immediately
|
# whole download to fail. However if the same fragment is immediately
|
||||||
# retried with the same request data this usually succeeds (1-2 attemps
|
# retried with the same request data this usually succeeds (1-2 attempts
|
||||||
# is usually enough) thus allowing to download the whole file successfully.
|
# is usually enough) thus allowing to download the whole file successfully.
|
||||||
# To be future-proof we will retry all fragments that fail with any
|
# To be future-proof we will retry all fragments that fail with any
|
||||||
# HTTP error.
|
# HTTP error.
|
||||||
|
@ -238,8 +238,8 @@ def write_metadata_tag(stream, metadata):
|
|||||||
|
|
||||||
|
|
||||||
def remove_encrypted_media(media):
|
def remove_encrypted_media(media):
|
||||||
return list(filter(lambda e: 'drmAdditionalHeaderId' not in e.attrib and
|
return list(filter(lambda e: 'drmAdditionalHeaderId' not in e.attrib
|
||||||
'drmAdditionalHeaderSetId' not in e.attrib,
|
and 'drmAdditionalHeaderSetId' not in e.attrib,
|
||||||
media))
|
media))
|
||||||
|
|
||||||
|
|
||||||
@ -267,8 +267,8 @@ class F4mFD(FragmentFD):
|
|||||||
media = doc.findall(_add_ns('media'))
|
media = doc.findall(_add_ns('media'))
|
||||||
if not media:
|
if not media:
|
||||||
self.report_error('No media found')
|
self.report_error('No media found')
|
||||||
for e in (doc.findall(_add_ns('drmAdditionalHeader')) +
|
for e in (doc.findall(_add_ns('drmAdditionalHeader'))
|
||||||
doc.findall(_add_ns('drmAdditionalHeaderSet'))):
|
+ doc.findall(_add_ns('drmAdditionalHeaderSet'))):
|
||||||
# If id attribute is missing it's valid for all media nodes
|
# If id attribute is missing it's valid for all media nodes
|
||||||
# without drmAdditionalHeaderId or drmAdditionalHeaderSetId attribute
|
# without drmAdditionalHeaderId or drmAdditionalHeaderSetId attribute
|
||||||
if 'id' not in e.attrib:
|
if 'id' not in e.attrib:
|
||||||
|
@ -219,8 +219,8 @@ class FragmentFD(FileDownloader):
|
|||||||
frag_total_bytes = s.get('total_bytes') or 0
|
frag_total_bytes = s.get('total_bytes') or 0
|
||||||
if not ctx['live']:
|
if not ctx['live']:
|
||||||
estimated_size = (
|
estimated_size = (
|
||||||
(ctx['complete_frags_downloaded_bytes'] + frag_total_bytes) /
|
(ctx['complete_frags_downloaded_bytes'] + frag_total_bytes)
|
||||||
(state['fragment_index'] + 1) * total_frags)
|
/ (state['fragment_index'] + 1) * total_frags)
|
||||||
state['total_bytes_estimate'] = estimated_size
|
state['total_bytes_estimate'] = estimated_size
|
||||||
|
|
||||||
if s['status'] == 'finished':
|
if s['status'] == 'finished':
|
||||||
|
@ -76,12 +76,12 @@ class HlsFD(FragmentFD):
|
|||||||
return fd.real_download(filename, info_dict)
|
return fd.real_download(filename, info_dict)
|
||||||
|
|
||||||
def is_ad_fragment_start(s):
|
def is_ad_fragment_start(s):
|
||||||
return (s.startswith('#ANVATO-SEGMENT-INFO') and 'type=ad' in s or
|
return (s.startswith('#ANVATO-SEGMENT-INFO') and 'type=ad' in s
|
||||||
s.startswith('#UPLYNK-SEGMENT') and s.endswith(',ad'))
|
or s.startswith('#UPLYNK-SEGMENT') and s.endswith(',ad'))
|
||||||
|
|
||||||
def is_ad_fragment_end(s):
|
def is_ad_fragment_end(s):
|
||||||
return (s.startswith('#ANVATO-SEGMENT-INFO') and 'type=master' in s or
|
return (s.startswith('#ANVATO-SEGMENT-INFO') and 'type=master' in s
|
||||||
s.startswith('#UPLYNK-SEGMENT') and s.endswith(',segment'))
|
or s.startswith('#UPLYNK-SEGMENT') and s.endswith(',segment'))
|
||||||
|
|
||||||
media_frags = 0
|
media_frags = 0
|
||||||
ad_frags = 0
|
ad_frags = 0
|
||||||
|
@ -46,8 +46,8 @@ class HttpFD(FileDownloader):
|
|||||||
|
|
||||||
is_test = self.params.get('test', False)
|
is_test = self.params.get('test', False)
|
||||||
chunk_size = self._TEST_FILE_SIZE if is_test else (
|
chunk_size = self._TEST_FILE_SIZE if is_test else (
|
||||||
info_dict.get('downloader_options', {}).get('http_chunk_size') or
|
info_dict.get('downloader_options', {}).get('http_chunk_size')
|
||||||
self.params.get('http_chunk_size') or 0)
|
or self.params.get('http_chunk_size') or 0)
|
||||||
|
|
||||||
ctx.open_mode = 'wb'
|
ctx.open_mode = 'wb'
|
||||||
ctx.resume_len = 0
|
ctx.resume_len = 0
|
||||||
@ -123,11 +123,11 @@ class HttpFD(FileDownloader):
|
|||||||
content_len = int_or_none(content_range_m.group(3))
|
content_len = int_or_none(content_range_m.group(3))
|
||||||
accept_content_len = (
|
accept_content_len = (
|
||||||
# Non-chunked download
|
# Non-chunked download
|
||||||
not ctx.chunk_size or
|
not ctx.chunk_size
|
||||||
# Chunked download and requested piece or
|
# Chunked download and requested piece or
|
||||||
# its part is promised to be served
|
# its part is promised to be served
|
||||||
content_range_end == range_end or
|
or content_range_end == range_end
|
||||||
content_len < range_end)
|
or content_len < range_end)
|
||||||
if accept_content_len:
|
if accept_content_len:
|
||||||
ctx.data_len = content_len
|
ctx.data_len = content_len
|
||||||
return
|
return
|
||||||
@ -152,8 +152,8 @@ class HttpFD(FileDownloader):
|
|||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
# Examine the reported length
|
# Examine the reported length
|
||||||
if (content_length is not None and
|
if (content_length is not None
|
||||||
(ctx.resume_len - 100 < int(content_length) < ctx.resume_len + 100)):
|
and (ctx.resume_len - 100 < int(content_length) < ctx.resume_len + 100)):
|
||||||
# The file had already been fully downloaded.
|
# The file had already been fully downloaded.
|
||||||
# Explanation to the above condition: in issue #175 it was revealed that
|
# Explanation to the above condition: in issue #175 it was revealed that
|
||||||
# YouTube sometimes adds or removes a few bytes from the end of the file,
|
# YouTube sometimes adds or removes a few bytes from the end of the file,
|
||||||
|
@ -146,7 +146,7 @@ def write_piff_header(stream, params):
|
|||||||
sps, pps = codec_private_data.split(u32.pack(1))[1:]
|
sps, pps = codec_private_data.split(u32.pack(1))[1:]
|
||||||
avcc_payload = u8.pack(1) # configuration version
|
avcc_payload = u8.pack(1) # configuration version
|
||||||
avcc_payload += sps[1:4] # avc profile indication + profile compatibility + avc level indication
|
avcc_payload += sps[1:4] # avc profile indication + profile compatibility + avc level indication
|
||||||
avcc_payload += u8.pack(0xfc | (params.get('nal_unit_length_field', 4) - 1)) # complete represenation (1) + reserved (11111) + length size minus one
|
avcc_payload += u8.pack(0xfc | (params.get('nal_unit_length_field', 4) - 1)) # complete representation (1) + reserved (11111) + length size minus one
|
||||||
avcc_payload += u8.pack(1) # reserved (0) + number of sps (0000001)
|
avcc_payload += u8.pack(1) # reserved (0) + number of sps (0000001)
|
||||||
avcc_payload += u16.pack(len(sps))
|
avcc_payload += u16.pack(len(sps))
|
||||||
avcc_payload += sps
|
avcc_payload += sps
|
||||||
|
@ -15,10 +15,13 @@ class AbcNewsVideoIE(AMPIE):
|
|||||||
IE_NAME = 'abcnews:video'
|
IE_NAME = 'abcnews:video'
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
https?://
|
https?://
|
||||||
abcnews\.go\.com/
|
|
||||||
(?:
|
(?:
|
||||||
[^/]+/video/(?P<display_id>[0-9a-z-]+)-|
|
abcnews\.go\.com/
|
||||||
video/embed\?.*?\bid=
|
(?:
|
||||||
|
[^/]+/video/(?P<display_id>[0-9a-z-]+)-|
|
||||||
|
video/embed\?.*?\bid=
|
||||||
|
)|
|
||||||
|
fivethirtyeight\.abcnews\.go\.com/video/embed/\d+/
|
||||||
)
|
)
|
||||||
(?P<id>\d+)
|
(?P<id>\d+)
|
||||||
'''
|
'''
|
||||||
|
@ -7,6 +7,7 @@ import functools
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_str
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
clean_html,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
try_get,
|
try_get,
|
||||||
@ -27,7 +28,7 @@ class ACastIE(InfoExtractor):
|
|||||||
'''
|
'''
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://www.acast.com/sparpodcast/2.raggarmordet-rosterurdetforflutna',
|
'url': 'https://www.acast.com/sparpodcast/2.raggarmordet-rosterurdetforflutna',
|
||||||
'md5': 'a02393c74f3bdb1801c3ec2695577ce0',
|
'md5': '16d936099ec5ca2d5869e3a813ee8dc4',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2a92b283-1a75-4ad8-8396-499c641de0d9',
|
'id': '2a92b283-1a75-4ad8-8396-499c641de0d9',
|
||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
@ -46,28 +47,37 @@ class ACastIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://play.acast.com/s/rattegangspodden/s04e09-styckmordet-i-helenelund-del-22',
|
'url': 'https://play.acast.com/s/rattegangspodden/s04e09-styckmordet-i-helenelund-del-22',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://play.acast.com/s/sparpodcast/2a92b283-1a75-4ad8-8396-499c641de0d9',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
channel, display_id = re.match(self._VALID_URL, url).groups()
|
channel, display_id = re.match(self._VALID_URL, url).groups()
|
||||||
s = self._download_json(
|
s = self._download_json(
|
||||||
'https://play-api.acast.com/stitch/%s/%s' % (channel, display_id),
|
'https://feeder.acast.com/api/v1/shows/%s/episodes/%s' % (channel, display_id),
|
||||||
display_id)['result']
|
display_id)
|
||||||
media_url = s['url']
|
media_url = s['url']
|
||||||
|
if re.search(r'[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}', display_id):
|
||||||
|
episode_url = s.get('episodeUrl')
|
||||||
|
if episode_url:
|
||||||
|
display_id = episode_url
|
||||||
|
else:
|
||||||
|
channel, display_id = re.match(self._VALID_URL, s['link']).groups()
|
||||||
cast_data = self._download_json(
|
cast_data = self._download_json(
|
||||||
'https://play-api.acast.com/splash/%s/%s' % (channel, display_id),
|
'https://play-api.acast.com/splash/%s/%s' % (channel, display_id),
|
||||||
display_id)['result']
|
display_id)['result']
|
||||||
e = cast_data['episode']
|
e = cast_data['episode']
|
||||||
title = e['name']
|
title = e.get('name') or s['title']
|
||||||
return {
|
return {
|
||||||
'id': compat_str(e['id']),
|
'id': compat_str(e['id']),
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'url': media_url,
|
'url': media_url,
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': e.get('description') or e.get('summary'),
|
'description': e.get('summary') or clean_html(e.get('description') or s.get('description')),
|
||||||
'thumbnail': e.get('image'),
|
'thumbnail': e.get('image'),
|
||||||
'timestamp': unified_timestamp(e.get('publishingDate')),
|
'timestamp': unified_timestamp(e.get('publishingDate') or s.get('publishDate')),
|
||||||
'duration': float_or_none(s.get('duration') or e.get('duration')),
|
'duration': float_or_none(e.get('duration') or s.get('duration')),
|
||||||
'filesize': int_or_none(e.get('contentLength')),
|
'filesize': int_or_none(e.get('contentLength')),
|
||||||
'creator': try_get(cast_data, lambda x: x['show']['author'], compat_str),
|
'creator': try_get(cast_data, lambda x: x['show']['author'], compat_str),
|
||||||
'series': try_get(cast_data, lambda x: x['show']['name'], compat_str),
|
'series': try_get(cast_data, lambda x: x['show']['name'], compat_str),
|
||||||
|
@ -59,9 +59,9 @@ class AddAnimeIE(InfoExtractor):
|
|||||||
parsed_url = compat_urllib_parse_urlparse(url)
|
parsed_url = compat_urllib_parse_urlparse(url)
|
||||||
av_val = av_res + len(parsed_url.netloc)
|
av_val = av_res + len(parsed_url.netloc)
|
||||||
confirm_url = (
|
confirm_url = (
|
||||||
parsed_url.scheme + '://' + parsed_url.netloc +
|
parsed_url.scheme + '://' + parsed_url.netloc
|
||||||
action + '?' +
|
+ action + '?'
|
||||||
compat_urllib_parse_urlencode({
|
+ compat_urllib_parse_urlencode({
|
||||||
'jschl_vc': vc, 'jschl_answer': compat_str(av_val)}))
|
'jschl_vc': vc, 'jschl_answer': compat_str(av_val)}))
|
||||||
self._download_webpage(
|
self._download_webpage(
|
||||||
confirm_url, video_id,
|
confirm_url, video_id,
|
||||||
|
@ -25,6 +25,11 @@ MSO_INFO = {
|
|||||||
'username_field': 'username',
|
'username_field': 'username',
|
||||||
'password_field': 'password',
|
'password_field': 'password',
|
||||||
},
|
},
|
||||||
|
'ATT': {
|
||||||
|
'name': 'AT&T U-verse',
|
||||||
|
'username_field': 'userid',
|
||||||
|
'password_field': 'password',
|
||||||
|
},
|
||||||
'ATTOTT': {
|
'ATTOTT': {
|
||||||
'name': 'DIRECTV NOW',
|
'name': 'DIRECTV NOW',
|
||||||
'username_field': 'email',
|
'username_field': 'email',
|
||||||
|
@ -4,17 +4,10 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_str
|
||||||
compat_parse_qs,
|
|
||||||
compat_str,
|
|
||||||
compat_urllib_parse_urlparse,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
find_xpath_attr,
|
|
||||||
get_element_by_attribute,
|
|
||||||
int_or_none,
|
int_or_none,
|
||||||
NO_DEFAULT,
|
|
||||||
qualities,
|
qualities,
|
||||||
try_get,
|
try_get,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
@ -25,59 +18,7 @@ from ..utils import (
|
|||||||
# add tests.
|
# add tests.
|
||||||
|
|
||||||
|
|
||||||
class ArteTvIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://videos\.arte\.tv/(?P<lang>fr|de|en|es)/.*-(?P<id>.*?)\.html'
|
|
||||||
IE_NAME = 'arte.tv'
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
lang = mobj.group('lang')
|
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
ref_xml_url = url.replace('/videos/', '/do_delegate/videos/')
|
|
||||||
ref_xml_url = ref_xml_url.replace('.html', ',view,asPlayerXml.xml')
|
|
||||||
ref_xml_doc = self._download_xml(
|
|
||||||
ref_xml_url, video_id, note='Downloading metadata')
|
|
||||||
config_node = find_xpath_attr(ref_xml_doc, './/video', 'lang', lang)
|
|
||||||
config_xml_url = config_node.attrib['ref']
|
|
||||||
config = self._download_xml(
|
|
||||||
config_xml_url, video_id, note='Downloading configuration')
|
|
||||||
|
|
||||||
formats = [{
|
|
||||||
'format_id': q.attrib['quality'],
|
|
||||||
# The playpath starts at 'mp4:', if we don't manually
|
|
||||||
# split the url, rtmpdump will incorrectly parse them
|
|
||||||
'url': q.text.split('mp4:', 1)[0],
|
|
||||||
'play_path': 'mp4:' + q.text.split('mp4:', 1)[1],
|
|
||||||
'ext': 'flv',
|
|
||||||
'quality': 2 if q.attrib['quality'] == 'hd' else 1,
|
|
||||||
} for q in config.findall('./urls/url')]
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
title = config.find('.//name').text
|
|
||||||
thumbnail = config.find('.//firstThumbnailUrl').text
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'formats': formats,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ArteTVBaseIE(InfoExtractor):
|
class ArteTVBaseIE(InfoExtractor):
|
||||||
@classmethod
|
|
||||||
def _extract_url_info(cls, url):
|
|
||||||
mobj = re.match(cls._VALID_URL, url)
|
|
||||||
lang = mobj.group('lang')
|
|
||||||
query = compat_parse_qs(compat_urllib_parse_urlparse(url).query)
|
|
||||||
if 'vid' in query:
|
|
||||||
video_id = query['vid'][0]
|
|
||||||
else:
|
|
||||||
# This is not a real id, it can be for example AJT for the news
|
|
||||||
# http://www.arte.tv/guide/fr/emissions/AJT/arte-journal
|
|
||||||
video_id = mobj.group('id')
|
|
||||||
return video_id, lang
|
|
||||||
|
|
||||||
def _extract_from_json_url(self, json_url, video_id, lang, title=None):
|
def _extract_from_json_url(self, json_url, video_id, lang, title=None):
|
||||||
info = self._download_json(json_url, video_id)
|
info = self._download_json(json_url, video_id)
|
||||||
player_info = info['videoJsonPlayer']
|
player_info = info['videoJsonPlayer']
|
||||||
@ -108,13 +49,15 @@ class ArteTVBaseIE(InfoExtractor):
|
|||||||
'upload_date': unified_strdate(upload_date_str),
|
'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'),
|
||||||
}
|
}
|
||||||
qfunc = qualities(['HQ', 'MQ', 'EQ', 'SQ'])
|
qfunc = qualities(['MQ', 'HQ', 'EQ', 'SQ'])
|
||||||
|
|
||||||
LANGS = {
|
LANGS = {
|
||||||
'fr': 'F',
|
'fr': 'F',
|
||||||
'de': 'A',
|
'de': 'A',
|
||||||
'en': 'E[ANG]',
|
'en': 'E[ANG]',
|
||||||
'es': 'E[ESP]',
|
'es': 'E[ESP]',
|
||||||
|
'it': 'E[ITA]',
|
||||||
|
'pl': 'E[POL]',
|
||||||
}
|
}
|
||||||
|
|
||||||
langcode = LANGS.get(lang, lang)
|
langcode = LANGS.get(lang, lang)
|
||||||
@ -126,8 +69,8 @@ class ArteTVBaseIE(InfoExtractor):
|
|||||||
l = re.escape(langcode)
|
l = re.escape(langcode)
|
||||||
|
|
||||||
# Language preference from most to least priority
|
# Language preference from most to least priority
|
||||||
# Reference: section 5.6.3 of
|
# Reference: section 6.8 of
|
||||||
# http://www.arte.tv/sites/en/corporate/files/complete-technical-guidelines-arte-geie-v1-05.pdf
|
# https://www.arte.tv/sites/en/corporate/files/complete-technical-guidelines-arte-geie-v1-07-1.pdf
|
||||||
PREFERENCES = (
|
PREFERENCES = (
|
||||||
# original version in requested language, without subtitles
|
# original version in requested language, without subtitles
|
||||||
r'VO{0}$'.format(l),
|
r'VO{0}$'.format(l),
|
||||||
@ -193,274 +136,59 @@ class ArteTVBaseIE(InfoExtractor):
|
|||||||
|
|
||||||
class ArteTVPlus7IE(ArteTVBaseIE):
|
class ArteTVPlus7IE(ArteTVBaseIE):
|
||||||
IE_NAME = 'arte.tv:+7'
|
IE_NAME = 'arte.tv:+7'
|
||||||
_VALID_URL = r'https?://(?:(?:www|sites)\.)?arte\.tv/(?:[^/]+/)?(?P<lang>fr|de|en|es)/(?:videos/)?(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
_VALID_URL = r'https?://(?:www\.)?arte\.tv/(?P<lang>fr|de|en|es|it|pl)/videos/(?P<id>\d{6}-\d{3}-[AF])'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.arte.tv/guide/de/sendungen/XEN/xenius/?vid=055918-015_PLUS7-D',
|
'url': 'https://www.arte.tv/en/videos/088501-000-A/mexico-stealing-petrol-to-survive/',
|
||||||
'only_matching': True,
|
'info_dict': {
|
||||||
}, {
|
'id': '088501-000-A',
|
||||||
'url': 'http://sites.arte.tv/karambolage/de/video/karambolage-22',
|
'ext': 'mp4',
|
||||||
'only_matching': True,
|
'title': 'Mexico: Stealing Petrol to Survive',
|
||||||
}, {
|
'upload_date': '20190628',
|
||||||
'url': 'http://www.arte.tv/de/videos/048696-000-A/der-kluge-bauch-unser-zweites-gehirn',
|
},
|
||||||
'only_matching': True,
|
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def suitable(cls, url):
|
|
||||||
return False if ArteTVPlaylistIE.suitable(url) else super(ArteTVPlus7IE, cls).suitable(url)
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id, lang = self._extract_url_info(url)
|
lang, video_id = re.match(self._VALID_URL, url).groups()
|
||||||
webpage = self._download_webpage(url, video_id)
|
return self._extract_from_json_url(
|
||||||
return self._extract_from_webpage(webpage, video_id, lang)
|
'https://api.arte.tv/api/player/v1/config/%s/%s' % (lang, video_id),
|
||||||
|
video_id, lang)
|
||||||
def _extract_from_webpage(self, webpage, video_id, lang):
|
|
||||||
patterns_templates = (r'arte_vp_url=["\'](.*?%s.*?)["\']', r'data-url=["\']([^"]+%s[^"]+)["\']')
|
|
||||||
ids = (video_id, '')
|
|
||||||
# some pages contain multiple videos (like
|
|
||||||
# http://www.arte.tv/guide/de/sendungen/XEN/xenius/?vid=055918-015_PLUS7-D),
|
|
||||||
# so we first try to look for json URLs that contain the video id from
|
|
||||||
# the 'vid' parameter.
|
|
||||||
patterns = [t % re.escape(_id) for _id in ids for t in patterns_templates]
|
|
||||||
json_url = self._html_search_regex(
|
|
||||||
patterns, webpage, 'json vp url', default=None)
|
|
||||||
if not json_url:
|
|
||||||
def find_iframe_url(webpage, default=NO_DEFAULT):
|
|
||||||
return self._html_search_regex(
|
|
||||||
r'<iframe[^>]+src=(["\'])(?P<url>.+\bjson_url=.+?)\1',
|
|
||||||
webpage, 'iframe url', group='url', default=default)
|
|
||||||
|
|
||||||
iframe_url = find_iframe_url(webpage, None)
|
|
||||||
if not iframe_url:
|
|
||||||
embed_url = self._html_search_regex(
|
|
||||||
r'arte_vp_url_oembed=\'([^\']+?)\'', webpage, 'embed url', default=None)
|
|
||||||
if embed_url:
|
|
||||||
player = self._download_json(
|
|
||||||
embed_url, video_id, 'Downloading player page')
|
|
||||||
iframe_url = find_iframe_url(player['html'])
|
|
||||||
# en and es URLs produce react-based pages with different layout (e.g.
|
|
||||||
# http://www.arte.tv/guide/en/053330-002-A/carnival-italy?zone=world)
|
|
||||||
if not iframe_url:
|
|
||||||
program = self._search_regex(
|
|
||||||
r'program\s*:\s*({.+?["\']embed_html["\'].+?}),?\s*\n',
|
|
||||||
webpage, 'program', default=None)
|
|
||||||
if program:
|
|
||||||
embed_html = self._parse_json(program, video_id)
|
|
||||||
if embed_html:
|
|
||||||
iframe_url = find_iframe_url(embed_html['embed_html'])
|
|
||||||
if iframe_url:
|
|
||||||
json_url = compat_parse_qs(
|
|
||||||
compat_urllib_parse_urlparse(iframe_url).query)['json_url'][0]
|
|
||||||
if json_url:
|
|
||||||
title = self._search_regex(
|
|
||||||
r'<h3[^>]+title=(["\'])(?P<title>.+?)\1',
|
|
||||||
webpage, 'title', default=None, group='title')
|
|
||||||
return self._extract_from_json_url(json_url, video_id, lang, title=title)
|
|
||||||
# Different kind of embed URL (e.g.
|
|
||||||
# http://www.arte.tv/magazine/trepalium/fr/episode-0406-replay-trepalium)
|
|
||||||
entries = [
|
|
||||||
self.url_result(url)
|
|
||||||
for _, url in re.findall(r'<iframe[^>]+src=(["\'])(?P<url>.+?)\1', webpage)]
|
|
||||||
return self.playlist_result(entries)
|
|
||||||
|
|
||||||
|
|
||||||
# It also uses the arte_vp_url url from the webpage to extract the information
|
|
||||||
class ArteTVCreativeIE(ArteTVPlus7IE):
|
|
||||||
IE_NAME = 'arte.tv:creative'
|
|
||||||
_VALID_URL = r'https?://creative\.arte\.tv/(?P<lang>fr|de|en|es)/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
|
||||||
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://creative.arte.tv/fr/episode/osmosis-episode-1',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '057405-001-A',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'OSMOSIS - N\'AYEZ PLUS PEUR D\'AIMER (1)',
|
|
||||||
'upload_date': '20150716',
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'url': 'http://creative.arte.tv/fr/Monty-Python-Reunion',
|
|
||||||
'playlist_count': 11,
|
|
||||||
'add_ie': ['Youtube'],
|
|
||||||
}, {
|
|
||||||
'url': 'http://creative.arte.tv/de/episode/agentur-amateur-4-der-erste-kunde',
|
|
||||||
'only_matching': True,
|
|
||||||
}]
|
|
||||||
|
|
||||||
|
|
||||||
class ArteTVInfoIE(ArteTVPlus7IE):
|
|
||||||
IE_NAME = 'arte.tv:info'
|
|
||||||
_VALID_URL = r'https?://info\.arte\.tv/(?P<lang>fr|de|en|es)/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
|
||||||
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://info.arte.tv/fr/service-civique-un-cache-misere',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '067528-000-A',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Service civique, un cache misère ?',
|
|
||||||
'upload_date': '20160403',
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
|
|
||||||
|
|
||||||
class ArteTVFutureIE(ArteTVPlus7IE):
|
|
||||||
IE_NAME = 'arte.tv:future'
|
|
||||||
_VALID_URL = r'https?://future\.arte\.tv/(?P<lang>fr|de|en|es)/(?P<id>[^/?#&]+)'
|
|
||||||
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://future.arte.tv/fr/info-sciences/les-ecrevisses-aussi-sont-anxieuses',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '050940-028-A',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Les écrevisses aussi peuvent être anxieuses',
|
|
||||||
'upload_date': '20140902',
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'url': 'http://future.arte.tv/fr/la-science-est-elle-responsable',
|
|
||||||
'only_matching': True,
|
|
||||||
}]
|
|
||||||
|
|
||||||
|
|
||||||
class ArteTVDDCIE(ArteTVPlus7IE):
|
|
||||||
IE_NAME = 'arte.tv:ddc'
|
|
||||||
_VALID_URL = r'https?://ddc\.arte\.tv/(?P<lang>emission|folge)/(?P<id>[^/?#&]+)'
|
|
||||||
|
|
||||||
_TESTS = []
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id, lang = self._extract_url_info(url)
|
|
||||||
if lang == 'folge':
|
|
||||||
lang = 'de'
|
|
||||||
elif lang == 'emission':
|
|
||||||
lang = 'fr'
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
scriptElement = get_element_by_attribute('class', 'visu_video_block', webpage)
|
|
||||||
script_url = self._html_search_regex(r'src="(.*?)"', scriptElement, 'script url')
|
|
||||||
javascriptPlayerGenerator = self._download_webpage(script_url, video_id, 'Download javascript player generator')
|
|
||||||
json_url = self._search_regex(r"json_url=(.*)&rendering_place.*", javascriptPlayerGenerator, 'json url')
|
|
||||||
return self._extract_from_json_url(json_url, video_id, lang)
|
|
||||||
|
|
||||||
|
|
||||||
class ArteTVConcertIE(ArteTVPlus7IE):
|
|
||||||
IE_NAME = 'arte.tv:concert'
|
|
||||||
_VALID_URL = r'https?://concert\.arte\.tv/(?P<lang>fr|de|en|es)/(?P<id>[^/?#&]+)'
|
|
||||||
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://concert.arte.tv/de/notwist-im-pariser-konzertclub-divan-du-monde',
|
|
||||||
'md5': '9ea035b7bd69696b67aa2ccaaa218161',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '186',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'The Notwist im Pariser Konzertclub "Divan du Monde"',
|
|
||||||
'upload_date': '20140128',
|
|
||||||
'description': 'md5:486eb08f991552ade77439fe6d82c305',
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
|
|
||||||
|
|
||||||
class ArteTVCinemaIE(ArteTVPlus7IE):
|
|
||||||
IE_NAME = 'arte.tv:cinema'
|
|
||||||
_VALID_URL = r'https?://cinema\.arte\.tv/(?P<lang>fr|de|en|es)/(?P<id>.+)'
|
|
||||||
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://cinema.arte.tv/fr/article/les-ailes-du-desir-de-julia-reck',
|
|
||||||
'md5': 'a5b9dd5575a11d93daf0e3f404f45438',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '062494-000-A',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Film lauréat du concours web - "Les ailes du désir" de Julia Reck',
|
|
||||||
'upload_date': '20150807',
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
|
|
||||||
|
|
||||||
class ArteTVMagazineIE(ArteTVPlus7IE):
|
|
||||||
IE_NAME = 'arte.tv:magazine'
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?arte\.tv/magazine/[^/]+/(?P<lang>fr|de|en|es)/(?P<id>[^/?#&]+)'
|
|
||||||
|
|
||||||
_TESTS = [{
|
|
||||||
# Embedded via <iframe src="http://www.arte.tv/arte_vp/index.php?json_url=..."
|
|
||||||
'url': 'http://www.arte.tv/magazine/trepalium/fr/entretien-avec-le-realisateur-vincent-lannoo-trepalium',
|
|
||||||
'md5': '2a9369bcccf847d1c741e51416299f25',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '065965-000-A',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Trepalium - Extrait Ep.01',
|
|
||||||
'upload_date': '20160121',
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
# Embedded via <iframe src="http://www.arte.tv/guide/fr/embed/054813-004-A/medium"
|
|
||||||
'url': 'http://www.arte.tv/magazine/trepalium/fr/episode-0406-replay-trepalium',
|
|
||||||
'md5': 'fedc64fc7a946110fe311634e79782ca',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '054813-004_PLUS7-F',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Trepalium (4/6)',
|
|
||||||
'description': 'md5:10057003c34d54e95350be4f9b05cb40',
|
|
||||||
'upload_date': '20160218',
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.arte.tv/magazine/metropolis/de/frank-woeste-german-paris-metropolis',
|
|
||||||
'only_matching': True,
|
|
||||||
}]
|
|
||||||
|
|
||||||
|
|
||||||
class ArteTVEmbedIE(ArteTVPlus7IE):
|
class ArteTVEmbedIE(ArteTVPlus7IE):
|
||||||
IE_NAME = 'arte.tv:embed'
|
IE_NAME = 'arte.tv:embed'
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
http://www\.arte\.tv
|
https://www\.arte\.tv
|
||||||
/(?:playerv2/embed|arte_vp/index)\.php\?json_url=
|
/player/v3/index\.php\?json_url=
|
||||||
(?P<json_url>
|
(?P<json_url>
|
||||||
http://arte\.tv/papi/tvguide/videos/stream/player/
|
https?://api\.arte\.tv/api/player/v1/config/
|
||||||
(?P<lang>[^/]+)/(?P<id>[^/]+)[^&]*
|
(?P<lang>[^/]+)/(?P<id>\d{6}-\d{3}-[AF])
|
||||||
)
|
)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
_TESTS = []
|
_TESTS = []
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
json_url, lang, video_id = re.match(self._VALID_URL, url).groups()
|
||||||
video_id = mobj.group('id')
|
|
||||||
lang = mobj.group('lang')
|
|
||||||
json_url = mobj.group('json_url')
|
|
||||||
return self._extract_from_json_url(json_url, video_id, lang)
|
return self._extract_from_json_url(json_url, video_id, lang)
|
||||||
|
|
||||||
|
|
||||||
class TheOperaPlatformIE(ArteTVPlus7IE):
|
|
||||||
IE_NAME = 'theoperaplatform'
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?theoperaplatform\.eu/(?P<lang>fr|de|en|es)/(?P<id>[^/?#&]+)'
|
|
||||||
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://www.theoperaplatform.eu/de/opera/verdi-otello',
|
|
||||||
'md5': '970655901fa2e82e04c00b955e9afe7b',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '060338-009-A',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Verdi - OTELLO',
|
|
||||||
'upload_date': '20160927',
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
|
|
||||||
|
|
||||||
class ArteTVPlaylistIE(ArteTVBaseIE):
|
class ArteTVPlaylistIE(ArteTVBaseIE):
|
||||||
IE_NAME = 'arte.tv:playlist'
|
IE_NAME = 'arte.tv:playlist'
|
||||||
_VALID_URL = r'https?://(?:www\.)?arte\.tv/guide/(?P<lang>fr|de|en|es)/[^#]*#collection/(?P<id>PL-\d+)'
|
_VALID_URL = r'https?://(?:www\.)?arte\.tv/(?P<lang>fr|de|en|es|it|pl)/videos/(?P<id>RC-\d{6})'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.arte.tv/guide/de/plus7/?country=DE#collection/PL-013263/ARTETV',
|
'url': 'https://www.arte.tv/en/videos/RC-016954/earn-a-living/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'PL-013263',
|
'id': 'RC-016954',
|
||||||
'title': 'Areva & Uramin',
|
'title': 'Earn a Living',
|
||||||
'description': 'md5:a1dc0312ce357c262259139cfd48c9bf',
|
'description': 'md5:d322c55011514b3a7241f7fb80d494c2',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 6,
|
'playlist_mincount': 6,
|
||||||
}, {
|
|
||||||
'url': 'http://www.arte.tv/guide/de/playlists?country=DE#collection/PL-013190/ARTETV',
|
|
||||||
'only_matching': True,
|
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
playlist_id, lang = self._extract_url_info(url)
|
lang, playlist_id = re.match(self._VALID_URL, url).groups()
|
||||||
collection = self._download_json(
|
collection = self._download_json(
|
||||||
'https://api.arte.tv/api/player/v1/collectionData/%s/%s?source=videos'
|
'https://api.arte.tv/api/player/v1/collectionData/%s/%s?source=videos'
|
||||||
% (lang, playlist_id), playlist_id)
|
% (lang, playlist_id), playlist_id)
|
||||||
|
@ -5,14 +5,12 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .kaltura import KalturaIE
|
from .kaltura import KalturaIE
|
||||||
from ..utils import (
|
from ..utils import extract_attributes
|
||||||
extract_attributes,
|
|
||||||
remove_end,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AsianCrushIE(InfoExtractor):
|
class AsianCrushIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?asiancrush\.com/video/(?:[^/]+/)?0+(?P<id>\d+)v\b'
|
_VALID_URL_BASE = r'https?://(?:www\.)?(?P<host>(?:(?:asiancrush|yuyutv|midnightpulp)\.com|cocoro\.tv))'
|
||||||
|
_VALID_URL = r'%s/video/(?:[^/]+/)?0+(?P<id>\d+)v\b' % _VALID_URL_BASE
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://www.asiancrush.com/video/012869v/women-who-flirt/',
|
'url': 'https://www.asiancrush.com/video/012869v/women-who-flirt/',
|
||||||
'md5': 'c3b740e48d0ba002a42c0b72857beae6',
|
'md5': 'c3b740e48d0ba002a42c0b72857beae6',
|
||||||
@ -20,7 +18,7 @@ class AsianCrushIE(InfoExtractor):
|
|||||||
'id': '1_y4tmjm5r',
|
'id': '1_y4tmjm5r',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Women Who Flirt',
|
'title': 'Women Who Flirt',
|
||||||
'description': 'md5:3db14e9186197857e7063522cb89a805',
|
'description': 'md5:7e986615808bcfb11756eb503a751487',
|
||||||
'timestamp': 1496936429,
|
'timestamp': 1496936429,
|
||||||
'upload_date': '20170608',
|
'upload_date': '20170608',
|
||||||
'uploader_id': 'craig@crifkin.com',
|
'uploader_id': 'craig@crifkin.com',
|
||||||
@ -28,10 +26,27 @@ class AsianCrushIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://www.asiancrush.com/video/she-was-pretty/011886v-pretty-episode-3/',
|
'url': 'https://www.asiancrush.com/video/she-was-pretty/011886v-pretty-episode-3/',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.yuyutv.com/video/013886v/the-act-of-killing/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.yuyutv.com/video/peep-show/013922v-warring-factions/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.midnightpulp.com/video/010400v/drifters/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.midnightpulp.com/video/mononoke/016378v-zashikiwarashi-part-1/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.cocoro.tv/video/the-wonderful-wizard-of-oz/008878v-the-wonderful-wizard-of-oz-ep01/',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
host = mobj.group('host')
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
@ -51,7 +66,7 @@ class AsianCrushIE(InfoExtractor):
|
|||||||
r'\bentry_id["\']\s*:\s*["\'](\d+)', webpage, 'entry id')
|
r'\bentry_id["\']\s*:\s*["\'](\d+)', webpage, 'entry id')
|
||||||
|
|
||||||
player = self._download_webpage(
|
player = self._download_webpage(
|
||||||
'https://api.asiancrush.com/embeddedVideoPlayer', video_id,
|
'https://api.%s/embeddedVideoPlayer' % host, video_id,
|
||||||
query={'id': entry_id})
|
query={'id': entry_id})
|
||||||
|
|
||||||
kaltura_id = self._search_regex(
|
kaltura_id = self._search_regex(
|
||||||
@ -63,15 +78,23 @@ class AsianCrushIE(InfoExtractor):
|
|||||||
r'/p(?:artner_id)?/(\d+)', player, 'partner id',
|
r'/p(?:artner_id)?/(\d+)', player, 'partner id',
|
||||||
default='513551')
|
default='513551')
|
||||||
|
|
||||||
return self.url_result(
|
description = self._html_search_regex(
|
||||||
'kaltura:%s:%s' % (partner_id, kaltura_id),
|
r'(?s)<div[^>]+\bclass=["\']description["\'][^>]*>(.+?)</div>',
|
||||||
ie=KalturaIE.ie_key(), video_id=kaltura_id,
|
webpage, 'description', fatal=False)
|
||||||
video_title=title)
|
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': 'kaltura:%s:%s' % (partner_id, kaltura_id),
|
||||||
|
'ie_key': KalturaIE.ie_key(),
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AsianCrushPlaylistIE(InfoExtractor):
|
class AsianCrushPlaylistIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?asiancrush\.com/series/0+(?P<id>\d+)s\b'
|
_VALID_URL = r'%s/series/0+(?P<id>\d+)s\b' % AsianCrushIE._VALID_URL_BASE
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'https://www.asiancrush.com/series/012481s/scholar-walks-night/',
|
'url': 'https://www.asiancrush.com/series/012481s/scholar-walks-night/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '12481',
|
'id': '12481',
|
||||||
@ -79,7 +102,16 @@ class AsianCrushPlaylistIE(InfoExtractor):
|
|||||||
'description': 'md5:7addd7c5132a09fd4741152d96cce886',
|
'description': 'md5:7addd7c5132a09fd4741152d96cce886',
|
||||||
},
|
},
|
||||||
'playlist_count': 20,
|
'playlist_count': 20,
|
||||||
}
|
}, {
|
||||||
|
'url': 'https://www.yuyutv.com/series/013920s/peep-show/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.midnightpulp.com/series/016375s/mononoke/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.cocoro.tv/series/008549s/the-wonderful-wizard-of-oz/',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
playlist_id = self._match_id(url)
|
playlist_id = self._match_id(url)
|
||||||
@ -96,15 +128,15 @@ class AsianCrushPlaylistIE(InfoExtractor):
|
|||||||
entries.append(self.url_result(
|
entries.append(self.url_result(
|
||||||
mobj.group('url'), ie=AsianCrushIE.ie_key()))
|
mobj.group('url'), ie=AsianCrushIE.ie_key()))
|
||||||
|
|
||||||
title = remove_end(
|
title = self._html_search_regex(
|
||||||
self._html_search_regex(
|
r'(?s)<h1\b[^>]\bid=["\']movieTitle[^>]+>(.+?)</h1>', webpage,
|
||||||
r'(?s)<h1\b[^>]\bid=["\']movieTitle[^>]+>(.+?)</h1>', webpage,
|
'title', default=None) or self._og_search_title(
|
||||||
'title', default=None) or self._og_search_title(
|
webpage, default=None) or self._html_search_meta(
|
||||||
webpage, default=None) or self._html_search_meta(
|
'twitter:title', webpage, 'title',
|
||||||
'twitter:title', webpage, 'title',
|
default=None) or self._search_regex(
|
||||||
default=None) or self._search_regex(
|
r'<title>([^<]+)</title>', webpage, 'title', fatal=False)
|
||||||
r'<title>([^<]+)</title>', webpage, 'title', fatal=False),
|
if title:
|
||||||
' | AsianCrush')
|
title = re.sub(r'\s*\|\s*.+?$', '', title)
|
||||||
|
|
||||||
description = self._og_search_description(
|
description = self._og_search_description(
|
||||||
webpage, default=None) or self._html_search_meta(
|
webpage, default=None) or self._html_search_meta(
|
||||||
|
@ -99,8 +99,8 @@ class BeamProLiveIE(BeamProBaseIE):
|
|||||||
|
|
||||||
class BeamProVodIE(BeamProBaseIE):
|
class BeamProVodIE(BeamProBaseIE):
|
||||||
IE_NAME = 'Mixer:vod'
|
IE_NAME = 'Mixer:vod'
|
||||||
_VALID_URL = r'https?://(?:\w+\.)?(?:beam\.pro|mixer\.com)/[^/?#&]+\?.*?\bvod=(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:\w+\.)?(?:beam\.pro|mixer\.com)/[^/?#&]+\?.*?\bvod=(?P<id>[^?#&]+)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'https://mixer.com/willow8714?vod=2259830',
|
'url': 'https://mixer.com/willow8714?vod=2259830',
|
||||||
'md5': 'b2431e6e8347dc92ebafb565d368b76b',
|
'md5': 'b2431e6e8347dc92ebafb565d368b76b',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -119,7 +119,13 @@ class BeamProVodIE(BeamProBaseIE):
|
|||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'url': 'https://mixer.com/streamer?vod=IxFno1rqC0S_XJ1a2yGgNw',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://mixer.com/streamer?vod=Rh3LY0VAqkGpEQUe2pN-ig',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_format(vod, vod_type):
|
def _extract_format(vod, vod_type):
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_str
|
from ..compat import (
|
||||||
|
compat_str,
|
||||||
|
compat_urlparse,
|
||||||
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
@ -11,6 +14,7 @@ from ..utils import (
|
|||||||
class BeegIE(InfoExtractor):
|
class BeegIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?beeg\.(?:com|porn(?:/video)?)/(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?beeg\.(?:com|porn(?:/video)?)/(?P<id>\d+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
|
# api/v6 v1
|
||||||
'url': 'http://beeg.com/5416503',
|
'url': 'http://beeg.com/5416503',
|
||||||
'md5': 'a1a1b1a8bc70a89e49ccfd113aed0820',
|
'md5': 'a1a1b1a8bc70a89e49ccfd113aed0820',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -24,6 +28,14 @@ class BeegIE(InfoExtractor):
|
|||||||
'tags': list,
|
'tags': list,
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
# api/v6 v2
|
||||||
|
'url': 'https://beeg.com/1941093077?t=911-1391',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# api/v6 v2 w/o t
|
||||||
|
'url': 'https://beeg.com/1277207756',
|
||||||
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://beeg.porn/video/5416503',
|
'url': 'https://beeg.porn/video/5416503',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@ -41,11 +53,25 @@ class BeegIE(InfoExtractor):
|
|||||||
r'beeg_version\s*=\s*([\da-zA-Z_-]+)', webpage, 'beeg version',
|
r'beeg_version\s*=\s*([\da-zA-Z_-]+)', webpage, 'beeg version',
|
||||||
default='1546225636701')
|
default='1546225636701')
|
||||||
|
|
||||||
|
if len(video_id) >= 10:
|
||||||
|
query = {
|
||||||
|
'v': 2,
|
||||||
|
}
|
||||||
|
qs = compat_urlparse.parse_qs(compat_urlparse.urlparse(url).query)
|
||||||
|
t = qs.get('t', [''])[0].split('-')
|
||||||
|
if len(t) > 1:
|
||||||
|
query.update({
|
||||||
|
's': t[0],
|
||||||
|
'e': t[1],
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
query = {'v': 1}
|
||||||
|
|
||||||
for api_path in ('', 'api.'):
|
for api_path in ('', 'api.'):
|
||||||
video = self._download_json(
|
video = self._download_json(
|
||||||
'https://%sbeeg.com/api/v6/%s/video/%s'
|
'https://%sbeeg.com/api/v6/%s/video/%s'
|
||||||
% (api_path, beeg_version, video_id), video_id,
|
% (api_path, beeg_version, video_id), video_id,
|
||||||
fatal=api_path == 'api.')
|
fatal=api_path == 'api.', query=query)
|
||||||
if video:
|
if video:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ from ..utils import (
|
|||||||
ExtractorError,
|
ExtractorError,
|
||||||
remove_end,
|
remove_end,
|
||||||
)
|
)
|
||||||
from .rudo import RudoIE
|
|
||||||
|
|
||||||
|
|
||||||
class BioBioChileTVIE(InfoExtractor):
|
class BioBioChileTVIE(InfoExtractor):
|
||||||
@ -41,11 +40,15 @@ class BioBioChileTVIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://www.biobiochile.cl/noticias/bbtv/comentarios-bio-bio/2016/07/08/edecanes-del-congreso-figuras-decorativas-que-le-cuestan-muy-caro-a-los-chilenos.shtml',
|
'url': 'http://www.biobiochile.cl/noticias/bbtv/comentarios-bio-bio/2016/07/08/edecanes-del-congreso-figuras-decorativas-que-le-cuestan-muy-caro-a-los-chilenos.shtml',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'edecanes-del-congreso-figuras-decorativas-que-le-cuestan-muy-caro-a-los-chilenos',
|
'id': 'b4xd0LK3SK',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'uploader': '(none)',
|
# TODO: fix url_transparent information overriding
|
||||||
'upload_date': '20160708',
|
# 'uploader': 'Juan Pablo Echenique',
|
||||||
'title': 'Edecanes del Congreso: Figuras decorativas que le cuestan muy caro a los chilenos',
|
'title': 'Comentario Oscar Cáceres',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# empty m3u8 manifest
|
||||||
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://tv.biobiochile.cl/notas/2015/10/22/ninos-transexuales-de-quien-es-la-decision.shtml',
|
'url': 'http://tv.biobiochile.cl/notas/2015/10/22/ninos-transexuales-de-quien-es-la-decision.shtml',
|
||||||
@ -60,7 +63,9 @@ class BioBioChileTVIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
rudo_url = RudoIE._extract_url(webpage)
|
rudo_url = self._search_regex(
|
||||||
|
r'<iframe[^>]+src=(?P<q1>[\'"])(?P<url>(?:https?:)?//rudo\.video/vod/[0-9a-zA-Z]+)(?P=q1)',
|
||||||
|
webpage, 'embed URL', None, group='url')
|
||||||
if not rudo_url:
|
if not rudo_url:
|
||||||
raise ExtractorError('No videos found')
|
raise ExtractorError('No videos found')
|
||||||
|
|
||||||
@ -68,7 +73,7 @@ class BioBioChileTVIE(InfoExtractor):
|
|||||||
|
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
uploader = self._html_search_regex(
|
uploader = self._html_search_regex(
|
||||||
r'<a[^>]+href=["\']https?://(?:busca|www)\.biobiochile\.cl/(?:lista/)?(?:author|autor)[^>]+>(.+?)</a>',
|
r'<a[^>]+href=["\'](?:https?://(?:busca|www)\.biobiochile\.cl)?/(?:lista/)?(?:author|autor)[^>]+>(.+?)</a>',
|
||||||
webpage, 'uploader', fatal=False)
|
webpage, 'uploader', fatal=False)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -42,7 +42,7 @@ class BIQLEIE(InfoExtractor):
|
|||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
embed_url = self._proto_relative_url(self._search_regex(
|
embed_url = self._proto_relative_url(self._search_regex(
|
||||||
r'<iframe.+?src="((?:https?:)?//daxab\.com/[^"]+)".*?></iframe>',
|
r'<iframe.+?src="((?:https?:)?//(?:daxab\.com|dxb\.to|[^/]+/player)/[^"]+)".*?></iframe>',
|
||||||
webpage, 'embed url'))
|
webpage, 'embed url'))
|
||||||
if VKIE.suitable(embed_url):
|
if VKIE.suitable(embed_url):
|
||||||
return self.url_result(embed_url, VKIE.ie_key(), video_id)
|
return self.url_result(embed_url, VKIE.ie_key(), video_id)
|
||||||
|
@ -55,6 +55,11 @@ class BitChuteIE(InfoExtractor):
|
|||||||
formats = [
|
formats = [
|
||||||
{'url': format_url}
|
{'url': format_url}
|
||||||
for format_url in orderedSet(format_urls)]
|
for format_url in orderedSet(format_urls)]
|
||||||
|
|
||||||
|
if not formats:
|
||||||
|
formats = self._parse_html5_media_entries(
|
||||||
|
url, webpage, video_id)[0]['formats']
|
||||||
|
|
||||||
self._check_formats(formats, video_id)
|
self._check_formats(formats, video_id)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
@ -65,8 +70,9 @@ class BitChuteIE(InfoExtractor):
|
|||||||
webpage, default=None) or self._html_search_meta(
|
webpage, default=None) or self._html_search_meta(
|
||||||
'twitter:image:src', webpage, 'thumbnail')
|
'twitter:image:src', webpage, 'thumbnail')
|
||||||
uploader = self._html_search_regex(
|
uploader = self._html_search_regex(
|
||||||
r'(?s)<p\b[^>]+\bclass=["\']video-author[^>]+>(.+?)</p>', webpage,
|
(r'(?s)<div class=["\']channel-banner.*?<p\b[^>]+\bclass=["\']name[^>]+>(.+?)</p>',
|
||||||
'uploader', fatal=False)
|
r'(?s)<p\b[^>]+\bclass=["\']video-author[^>]+>(.+?)</p>'),
|
||||||
|
webpage, 'uploader', fatal=False)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
@ -71,7 +71,7 @@ class BleacherReportIE(InfoExtractor):
|
|||||||
video = article_data.get('video')
|
video = article_data.get('video')
|
||||||
if video:
|
if video:
|
||||||
video_type = video['type']
|
video_type = video['type']
|
||||||
if video_type == 'cms.bleacherreport.com':
|
if video_type in ('cms.bleacherreport.com', 'vid.bleacherreport.com'):
|
||||||
info['url'] = 'http://bleacherreport.com/video_embed?id=%s' % video['id']
|
info['url'] = 'http://bleacherreport.com/video_embed?id=%s' % video['id']
|
||||||
elif video_type == 'ooyala.com':
|
elif video_type == 'ooyala.com':
|
||||||
info['url'] = 'ooyala:%s' % video['id']
|
info['url'] = 'ooyala:%s' % video['id']
|
||||||
@ -87,9 +87,9 @@ class BleacherReportIE(InfoExtractor):
|
|||||||
|
|
||||||
|
|
||||||
class BleacherReportCMSIE(AMPIE):
|
class BleacherReportCMSIE(AMPIE):
|
||||||
_VALID_URL = r'https?://(?:www\.)?bleacherreport\.com/video_embed\?id=(?P<id>[0-9a-f-]{36})'
|
_VALID_URL = r'https?://(?:www\.)?bleacherreport\.com/video_embed\?id=(?P<id>[0-9a-f-]{36}|\d{5})'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://bleacherreport.com/video_embed?id=8fd44c2f-3dc5-4821-9118-2c825a98c0e1',
|
'url': 'http://bleacherreport.com/video_embed?id=8fd44c2f-3dc5-4821-9118-2c825a98c0e1&library=video-cms',
|
||||||
'md5': '2e4b0a997f9228ffa31fada5c53d1ed1',
|
'md5': '2e4b0a997f9228ffa31fada5c53d1ed1',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '8fd44c2f-3dc5-4821-9118-2c825a98c0e1',
|
'id': '8fd44c2f-3dc5-4821-9118-2c825a98c0e1',
|
||||||
@ -101,6 +101,6 @@ class BleacherReportCMSIE(AMPIE):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
info = self._extract_feed_info('http://cms.bleacherreport.com/media/items/%s/akamai.json' % video_id)
|
info = self._extract_feed_info('http://vid.bleacherreport.com/videos/%s.akamai' % video_id)
|
||||||
info['id'] = video_id
|
info['id'] = video_id
|
||||||
return info
|
return info
|
||||||
|
@ -32,8 +32,8 @@ class BlinkxIE(InfoExtractor):
|
|||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
display_id = video_id[:8]
|
display_id = video_id[:8]
|
||||||
|
|
||||||
api_url = ('https://apib4.blinkx.com/api.php?action=play_video&' +
|
api_url = ('https://apib4.blinkx.com/api.php?action=play_video&'
|
||||||
'video=%s' % video_id)
|
+ 'video=%s' % video_id)
|
||||||
data_json = self._download_webpage(api_url, display_id)
|
data_json = self._download_webpage(api_url, display_id)
|
||||||
data = json.loads(data_json)['api']['results'][0]
|
data = json.loads(data_json)['api']['results'][0]
|
||||||
duration = None
|
duration = None
|
||||||
|
@ -483,7 +483,7 @@ class BrightcoveLegacyIE(InfoExtractor):
|
|||||||
|
|
||||||
class BrightcoveNewIE(AdobePassIE):
|
class BrightcoveNewIE(AdobePassIE):
|
||||||
IE_NAME = 'brightcove:new'
|
IE_NAME = 'brightcove:new'
|
||||||
_VALID_URL = r'https?://players\.brightcove\.net/(?P<account_id>\d+)/(?P<player_id>[^/]+)_(?P<embed>[^/]+)/index\.html\?.*videoId=(?P<video_id>\d+|ref:[^&]+)'
|
_VALID_URL = r'https?://players\.brightcove\.net/(?P<account_id>\d+)/(?P<player_id>[^/]+)_(?P<embed>[^/]+)/index\.html\?.*(?P<content_type>video|playlist)Id=(?P<video_id>\d+|ref:[^&]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://players.brightcove.net/929656772001/e41d32dc-ec74-459e-a845-6c69f7b724ea_default/index.html?videoId=4463358922001',
|
'url': 'http://players.brightcove.net/929656772001/e41d32dc-ec74-459e-a845-6c69f7b724ea_default/index.html?videoId=4463358922001',
|
||||||
'md5': 'c8100925723840d4b0d243f7025703be',
|
'md5': 'c8100925723840d4b0d243f7025703be',
|
||||||
@ -516,6 +516,21 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
# m3u8 download
|
# m3u8 download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
# playlist stream
|
||||||
|
'url': 'https://players.brightcove.net/1752604059001/S13cJdUBz_default/index.html?playlistId=5718313430001',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '5718313430001',
|
||||||
|
'title': 'No Audio Playlist',
|
||||||
|
},
|
||||||
|
'playlist_count': 7,
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://players.brightcove.net/5690807595001/HyZNerRl7_default/index.html?playlistId=5743160747001',
|
||||||
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
# ref: prefixed video id
|
# ref: prefixed video id
|
||||||
'url': 'http://players.brightcove.net/3910869709001/21519b5c-4b3b-4363-accb-bdc8f358f823_default/index.html?videoId=ref:7069442',
|
'url': 'http://players.brightcove.net/3910869709001/21519b5c-4b3b-4363-accb-bdc8f358f823_default/index.html?videoId=ref:7069442',
|
||||||
@ -715,7 +730,7 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
'ip_blocks': smuggled_data.get('geo_ip_blocks'),
|
'ip_blocks': smuggled_data.get('geo_ip_blocks'),
|
||||||
})
|
})
|
||||||
|
|
||||||
account_id, player_id, embed, video_id = re.match(self._VALID_URL, url).groups()
|
account_id, player_id, embed, content_type, video_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
|
||||||
webpage = self._download_webpage(
|
webpage = self._download_webpage(
|
||||||
'http://players.brightcove.net/%s/%s_%s/index.min.js'
|
'http://players.brightcove.net/%s/%s_%s/index.min.js'
|
||||||
@ -736,7 +751,7 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
r'policyKey\s*:\s*(["\'])(?P<pk>.+?)\1',
|
r'policyKey\s*:\s*(["\'])(?P<pk>.+?)\1',
|
||||||
webpage, 'policy key', group='pk')
|
webpage, 'policy key', group='pk')
|
||||||
|
|
||||||
api_url = 'https://edge.api.brightcove.com/playback/v1/accounts/%s/videos/%s' % (account_id, video_id)
|
api_url = 'https://edge.api.brightcove.com/playback/v1/accounts/%s/%ss/%s' % (account_id, content_type, video_id)
|
||||||
headers = {
|
headers = {
|
||||||
'Accept': 'application/json;pk=%s' % policy_key,
|
'Accept': 'application/json;pk=%s' % policy_key,
|
||||||
}
|
}
|
||||||
@ -771,5 +786,12 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
'tveToken': tve_token,
|
'tveToken': tve_token,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if content_type == 'playlist':
|
||||||
|
return self.playlist_result(
|
||||||
|
[self._parse_brightcove_metadata(vid, vid.get('id'), headers)
|
||||||
|
for vid in json_data.get('videos', []) if vid.get('id')],
|
||||||
|
json_data.get('id'), json_data.get('name'),
|
||||||
|
json_data.get('description'))
|
||||||
|
|
||||||
return self._parse_brightcove_metadata(
|
return self._parse_brightcove_metadata(
|
||||||
json_data, video_id, headers=headers)
|
json_data, video_id, headers=headers)
|
||||||
|
@ -3,11 +3,13 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import parse_duration
|
||||||
|
|
||||||
|
|
||||||
class BYUtvIE(InfoExtractor):
|
class BYUtvIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?byutv\.org/(?:watch|player)/(?!event/)(?P<id>[0-9a-f-]+)(?:/(?P<display_id>[^/?#&]+))?'
|
_VALID_URL = r'https?://(?:www\.)?byutv\.org/(?:watch|player)/(?!event/)(?P<id>[0-9a-f-]+)(?:/(?P<display_id>[^/?#&]+))?'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
|
# ooyalaVOD
|
||||||
'url': 'http://www.byutv.org/watch/6587b9a3-89d2-42a6-a7f7-fd2f81840a7d/studio-c-season-5-episode-5',
|
'url': 'http://www.byutv.org/watch/6587b9a3-89d2-42a6-a7f7-fd2f81840a7d/studio-c-season-5-episode-5',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'ZvanRocTpW-G5_yZFeltTAMv6jxOU9KH',
|
'id': 'ZvanRocTpW-G5_yZFeltTAMv6jxOU9KH',
|
||||||
@ -22,6 +24,20 @@ class BYUtvIE(InfoExtractor):
|
|||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
'add_ie': ['Ooyala'],
|
'add_ie': ['Ooyala'],
|
||||||
|
}, {
|
||||||
|
# dvr
|
||||||
|
'url': 'https://www.byutv.org/player/8f1dab9b-b243-47c8-b525-3e2d021a3451/byu-softball-pacific-vs-byu-41219---game-2',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '8f1dab9b-b243-47c8-b525-3e2d021a3451',
|
||||||
|
'display_id': 'byu-softball-pacific-vs-byu-41219---game-2',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Pacific vs. BYU (4/12/19)',
|
||||||
|
'description': 'md5:1ac7b57cb9a78015910a4834790ce1f3',
|
||||||
|
'duration': 11645,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.byutv.org/watch/6587b9a3-89d2-42a6-a7f7-fd2f81840a7d',
|
'url': 'http://www.byutv.org/watch/6587b9a3-89d2-42a6-a7f7-fd2f81840a7d',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@ -35,24 +51,42 @@ class BYUtvIE(InfoExtractor):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
display_id = mobj.group('display_id') or video_id
|
display_id = mobj.group('display_id') or video_id
|
||||||
|
|
||||||
ep = self._download_json(
|
info = self._download_json(
|
||||||
'https://api.byutv.org/api3/catalog/getvideosforcontent', video_id,
|
'https://api.byutv.org/api3/catalog/getvideosforcontent',
|
||||||
query={
|
display_id, query={
|
||||||
'contentid': video_id,
|
'contentid': video_id,
|
||||||
'channel': 'byutv',
|
'channel': 'byutv',
|
||||||
'x-byutv-context': 'web$US',
|
'x-byutv-context': 'web$US',
|
||||||
}, headers={
|
}, headers={
|
||||||
'x-byutv-context': 'web$US',
|
'x-byutv-context': 'web$US',
|
||||||
'x-byutv-platformkey': 'xsaaw9c7y5',
|
'x-byutv-platformkey': 'xsaaw9c7y5',
|
||||||
})['ooyalaVOD']
|
})
|
||||||
|
|
||||||
|
ep = info.get('ooyalaVOD')
|
||||||
|
if ep:
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'ie_key': 'Ooyala',
|
||||||
|
'url': 'ooyala:%s' % ep['providerId'],
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': ep.get('title'),
|
||||||
|
'description': ep.get('description'),
|
||||||
|
'thumbnail': ep.get('imageThumbnail'),
|
||||||
|
}
|
||||||
|
|
||||||
|
ep = info['dvr']
|
||||||
|
title = ep['title']
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
ep['videoUrl'], video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
|
m3u8_id='hls')
|
||||||
|
self._sort_formats(formats)
|
||||||
return {
|
return {
|
||||||
'_type': 'url_transparent',
|
|
||||||
'ie_key': 'Ooyala',
|
|
||||||
'url': 'ooyala:%s' % ep['providerId'],
|
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': ep.get('title'),
|
'title': title,
|
||||||
'description': ep.get('description'),
|
'description': ep.get('description'),
|
||||||
'thumbnail': ep.get('imageThumbnail'),
|
'thumbnail': ep.get('imageThumbnail'),
|
||||||
|
'duration': parse_duration(ep.get('length')),
|
||||||
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class CanvasIE(InfoExtractor):
|
class CanvasIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://mediazone\.vrt\.be/api/v1/(?P<site_id>canvas|een|ketnet|vrtvideo)/assets/(?P<id>[^/?#&]+)'
|
_VALID_URL = r'https?://mediazone\.vrt\.be/api/v1/(?P<site_id>canvas|een|ketnet|vrt(?:video|nieuws)|sporza)/assets/(?P<id>[^/?#&]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://mediazone.vrt.be/api/v1/ketnet/assets/md-ast-4ac54990-ce66-4d00-a8ca-9eac86f4c475',
|
'url': 'https://mediazone.vrt.be/api/v1/ketnet/assets/md-ast-4ac54990-ce66-4d00-a8ca-9eac86f4c475',
|
||||||
'md5': '90139b746a0a9bd7bb631283f6e2a64e',
|
'md5': '90139b746a0a9bd7bb631283f6e2a64e',
|
||||||
@ -35,6 +35,10 @@ class CanvasIE(InfoExtractor):
|
|||||||
'url': 'https://mediazone.vrt.be/api/v1/canvas/assets/mz-ast-5e5f90b6-2d72-4c40-82c2-e134f884e93e',
|
'url': 'https://mediazone.vrt.be/api/v1/canvas/assets/mz-ast-5e5f90b6-2d72-4c40-82c2-e134f884e93e',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
_HLS_ENTRY_PROTOCOLS_MAP = {
|
||||||
|
'HLS': 'm3u8_native',
|
||||||
|
'HLS_AES': 'm3u8',
|
||||||
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
@ -52,9 +56,9 @@ class CanvasIE(InfoExtractor):
|
|||||||
format_url, format_type = target.get('url'), target.get('type')
|
format_url, format_type = target.get('url'), target.get('type')
|
||||||
if not format_url or not format_type:
|
if not format_url or not format_type:
|
||||||
continue
|
continue
|
||||||
if format_type == 'HLS':
|
if format_type in self._HLS_ENTRY_PROTOCOLS_MAP:
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
format_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
format_url, video_id, 'mp4', self._HLS_ENTRY_PROTOCOLS_MAP[format_type],
|
||||||
m3u8_id=format_type, fatal=False))
|
m3u8_id=format_type, fatal=False))
|
||||||
elif format_type == 'HDS':
|
elif format_type == 'HDS':
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
@ -69,7 +69,7 @@ class CBSIE(CBSBaseIE):
|
|||||||
last_e = None
|
last_e = None
|
||||||
for item in items_data.findall('.//item'):
|
for item in items_data.findall('.//item'):
|
||||||
asset_type = xpath_text(item, 'assetType')
|
asset_type = xpath_text(item, 'assetType')
|
||||||
if not asset_type or asset_type in asset_types or asset_type in ('HLS_FPS', 'DASH_CENC'):
|
if not asset_type or asset_type in asset_types or 'HLS_FPS' in asset_type or 'DASH_CENC' in asset_type:
|
||||||
continue
|
continue
|
||||||
asset_types.append(asset_type)
|
asset_types.append(asset_type)
|
||||||
query = {
|
query = {
|
||||||
|
@ -1,40 +1,62 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import zlib
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .cbs import CBSIE
|
from .cbs import CBSIE
|
||||||
|
from ..compat import (
|
||||||
|
compat_b64decode,
|
||||||
|
compat_urllib_parse_unquote,
|
||||||
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
parse_duration,
|
parse_duration,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CBSNewsEmbedIE(CBSIE):
|
||||||
|
IE_NAME = 'cbsnews:embed'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?cbsnews\.com/embed/video[^#]*#(?P<id>.+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.cbsnews.com/embed/video/?v=1.c9b5b61492913d6660db0b2f03579ef25e86307a#1Vb7b9s2EP5XBAHbT6Gt98PAMKTJ0se6LVjWYWtdGBR1stlIpEBSTtwi%2F%2FvuJNkNhmHdGxgM2NL57vjd6zt%2B8PngdN%2Fyg79qeGvhzN%2FLGrS%2F%2BuBLB531V28%2B%2BO7Qg7%2Fy97r2z3xZ42NW8yLhDbA0S0KWlHnIijwKWJBHZZnHBa8Cgbpdf%2F89NM9Hi9fXifhpr8sr%2FlP848tn%2BTdXycX25zh4cdX%2FvHl6PmmPqnWQv9w8Ed%2B9GjYRim07bFEqdG%2BZVHuwTm65A7bVRrYtR5lAyMox7pigF6W4k%2By91mjspGsJ%2BwVae4%2BsvdnaO1p73HkXs%2FVisUDTGm7R8IcdnOROeq%2B19qT1amhA1VJtPenoTUgrtfKc9m7Rq8dP7nnjwOB7wg7ADdNt7VX64DWAWlKhPtmDEq22g4GF99x6Dk9E8OSsankHXqPNKDxC%2FdK7MLKTircTDgsI3mmj4OBdSq64dy7fd1x577RU1rt4cvMtOaulFYOd%2FLewRWvDO9lIgXFpZSnkZmjbv5SxKTPoQXClFbpsf%2Fhbbpzs0IB3vb8KkyzJQ%2BywOAgCrMpgRrz%2BKk4fvb7kFbR4XJCu0gAdtNO7woCwZTu%2BBUs9bam%2Fds71drVerpeisgrubLjAB4nnOSkWQnfr5W6o1ku5Xpr1MgrCbL0M0vUyDtfLLK15WiYp47xKWSLyjFVpwVmVJSLIoCjSOFkv3W7oKsVliwZJcB9nwXpZ5GEQQwY8jNKqKCBrgjTLeFxgdCIpazojDgnRtn43J6kG7nZ6cAbxh0EeFFk4%2B1u867cY5u4344n%2FxXjCqAjucdTHgLKojNKmSfO8KRsOFY%2FzKEYCKEJBzv90QA9nfm9gL%2BHulaFqUkz9ULUYxl62B3U%2FRVNLA8IhggaPycOoBuwOCESciDQVSSUgiOMsROB%2FhKfwCKOzEk%2B4k6rWd4uuT%2FwTDz7K7t3d3WLO8ISD95jSPQbayBacthbz86XVgxHwhex5zawzgDOmtp%2F3GPcXn0VXHdSS029%2Fj99UC%2FwJUvyKQ%2FzKyixIEVlYJOn4RxxuaH43Ty9fbJ5OObykHH435XAzJTHeOF4hhEUXD8URe%2FQ%2FBT%2BMpf8d5GN02Ox%2FfiGsl7TA7POu1xZ5%2BbTzcAVKMe48mqcC21hkacVEVScM26liVVBnrKkC4CLKyzAvHu0lhEaTKMFwI3a4SN9MsrfYzdBLq2vkwRD1gVviLT8kY9h2CHH6Y%2Bix6609weFtey4ESp60WtyeWMy%2BsmBuhsoKIyuoT%2Bq2R%2FrW5qi3g%2FvzS2j40DoixDP8%2BKP0yUdpXJ4l6Vla%2Bg9vce%2BC4yM5YlUcbA%2F0jLKdpmTwvsdN5z88nAIe08%2F0HgxeG1iv%2B6Hlhjh7uiW0SDzYNI92L401uha3JKYk268UVRzdOzNQvAaJqoXzAc80dAV440NZ1WVVAAMRYQ2KrGJFmDUsq8saWSnjvIj8t78y%2FRa3JRnbHVfyFpfwoDiGpPgjzekyUiKNlU3OMlwuLMmzgvEojllYVE2Z1HhImvsnk%2BuhusTEoB21PAtSFodeFK3iYhXEH9WOG2%2FkOE833sfeG%2Ff5cfHtEFNXgYes0%2FXj7aGivUgJ9XpusCtoNcNYVVnJVrrDo0OmJAutHCpuZul4W9lLcfy7BnuLPT02%2ByXsCTk%2B9zhzswIN04YueNSK%2BPtM0jS88QdLqSLJDTLsuGZJNolm2yO0PXh3UPnz9Ix5bfIAqxPjvETQsDCEiPG4QbqNyhBZISxybLnZYCrW5H3Axp690%2F0BJdXtDZ5ITuM4xj3f4oUHGzc5JeJmZKpp%2FjwKh4wMV%2FV1yx3emLoR0MwbG4K%2F%2BZgVep3PnzXGDHZ6a3i%2Fk%2BJrONDN13%2Bnq6tBTYk4o7cLGhBtqCC4KwacGHpEVuoH5JNro%2FE6JfE6d5RydbiR76k%2BW5wioDHBIjw1euhHjUGRB0y5A97KoaPx6MlL%2BwgboUVtUFRI%2FLemgTpdtF59ii7pab08kuPcfWzs0l%2FRI5takWnFpka0zOgWRtYcuf9aIxZMxlwr6IiGpsb6j2DQUXPl%2FimXI599Ev7fWjoPD78A',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
item = self._parse_json(zlib.decompress(compat_b64decode(
|
||||||
|
compat_urllib_parse_unquote(self._match_id(url))),
|
||||||
|
-zlib.MAX_WBITS), None)['video']['items'][0]
|
||||||
|
return self._extract_video_info(item['mpxRefId'], 'cbsnews')
|
||||||
|
|
||||||
|
|
||||||
class CBSNewsIE(CBSIE):
|
class CBSNewsIE(CBSIE):
|
||||||
IE_NAME = 'cbsnews'
|
IE_NAME = 'cbsnews'
|
||||||
IE_DESC = 'CBS News'
|
IE_DESC = 'CBS News'
|
||||||
_VALID_URL = r'https?://(?:www\.)?cbsnews\.com/(?:news|videos)/(?P<id>[\da-z_-]+)'
|
_VALID_URL = r'https?://(?:www\.)?cbsnews\.com/(?:news|video)/(?P<id>[\da-z_-]+)'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
# 60 minutes
|
# 60 minutes
|
||||||
'url': 'http://www.cbsnews.com/news/artificial-intelligence-positioned-to-be-a-game-changer/',
|
'url': 'http://www.cbsnews.com/news/artificial-intelligence-positioned-to-be-a-game-changer/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '_B6Ga3VJrI4iQNKsir_cdFo9Re_YJHE_',
|
'id': 'Y_nf_aEg6WwO9OLAq0MpKaPgfnBUxfW4',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'title': 'Artificial Intelligence',
|
'title': 'Artificial Intelligence, real-life applications',
|
||||||
'description': 'md5:8818145f9974431e0fb58a1b8d69613c',
|
'description': 'md5:a7aaf27f1b4777244de8b0b442289304',
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
'duration': 1606,
|
'duration': 317,
|
||||||
'uploader': 'CBSI-NEW',
|
'uploader': 'CBSI-NEW',
|
||||||
'timestamp': 1498431900,
|
'timestamp': 1476046464,
|
||||||
'upload_date': '20170625',
|
'upload_date': '20161009',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# m3u8 download
|
# rtmp download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://www.cbsnews.com/videos/fort-hood-shooting-army-downplays-mental-illness-as-cause-of-attack/',
|
'url': 'https://www.cbsnews.com/video/fort-hood-shooting-army-downplays-mental-illness-as-cause-of-attack/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'SNJBOYzXiWBOvaLsdzwH8fmtP1SCd91Y',
|
'id': 'SNJBOYzXiWBOvaLsdzwH8fmtP1SCd91Y',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@ -60,37 +82,29 @@ class CBSNewsIE(CBSIE):
|
|||||||
# 48 hours
|
# 48 hours
|
||||||
'url': 'http://www.cbsnews.com/news/maria-ridulph-murder-will-the-nations-oldest-cold-case-to-go-to-trial-ever-get-solved/',
|
'url': 'http://www.cbsnews.com/news/maria-ridulph-murder-will-the-nations-oldest-cold-case-to-go-to-trial-ever-get-solved/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'QpM5BJjBVEAUFi7ydR9LusS69DPLqPJ1',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Cold as Ice',
|
'title': 'Cold as Ice',
|
||||||
'description': 'Can a childhood memory of a friend\'s murder solve a 1957 cold case? "48 Hours" correspondent Erin Moriarty has the latest.',
|
'description': 'Can a childhood memory solve the 1957 murder of 7-year-old Maria Ridulph?',
|
||||||
'upload_date': '20170604',
|
|
||||||
'timestamp': 1496538000,
|
|
||||||
'uploader': 'CBSI-NEW',
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
},
|
||||||
|
'playlist_mincount': 7,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
video_info = self._parse_json(self._html_search_regex(
|
entries = []
|
||||||
r'(?:<ul class="media-list items" id="media-related-items"[^>]*><li data-video-info|<div id="cbsNewsVideoPlayer" data-video-player-options)=\'({.+?})\'',
|
for embed_url in re.findall(r'<iframe[^>]+data-src="(https?://(?:www\.)?cbsnews\.com/embed/video/[^#]*#[^"]+)"', webpage):
|
||||||
webpage, 'video JSON info', default='{}'), video_id, fatal=False)
|
entries.append(self.url_result(embed_url, CBSNewsEmbedIE.ie_key()))
|
||||||
|
if entries:
|
||||||
if video_info:
|
return self.playlist_result(
|
||||||
item = video_info['item'] if 'item' in video_info else video_info
|
entries, playlist_title=self._html_search_meta(['og:title', 'twitter:title'], webpage),
|
||||||
else:
|
playlist_description=self._html_search_meta(['og:description', 'twitter:description', 'description'], webpage))
|
||||||
state = self._parse_json(self._search_regex(
|
|
||||||
r'data-cbsvideoui-options=(["\'])(?P<json>{.+?})\1', webpage,
|
|
||||||
'playlist JSON info', group='json'), video_id)['state']
|
|
||||||
item = state['playlist'][state['pid']]
|
|
||||||
|
|
||||||
|
item = self._parse_json(self._html_search_regex(
|
||||||
|
r'CBSNEWS\.defaultPayload\s*=\s*({.+})',
|
||||||
|
webpage, 'video JSON info'), display_id)['items'][0]
|
||||||
return self._extract_video_info(item['mpxRefId'], 'cbsnews')
|
return self._extract_video_info(item['mpxRefId'], 'cbsnews')
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,8 +10,8 @@ class CloudflareStreamIE(InfoExtractor):
|
|||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
https?://
|
https?://
|
||||||
(?:
|
(?:
|
||||||
(?:watch\.)?cloudflarestream\.com/|
|
(?:watch\.)?(?:cloudflarestream\.com|videodelivery\.net)/|
|
||||||
embed\.cloudflarestream\.com/embed/[^/]+\.js\?.*?\bvideo=
|
embed\.(?:cloudflarestream\.com|videodelivery\.net)/embed/[^/]+\.js\?.*?\bvideo=
|
||||||
)
|
)
|
||||||
(?P<id>[\da-f]+)
|
(?P<id>[\da-f]+)
|
||||||
'''
|
'''
|
||||||
@ -31,6 +31,9 @@ class CloudflareStreamIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://cloudflarestream.com/31c9291ab41fac05471db4e73aa11717/manifest/video.mpd',
|
'url': 'https://cloudflarestream.com/31c9291ab41fac05471db4e73aa11717/manifest/video.mpd',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://embed.videodelivery.net/embed/r4xu.fla9.latest.js?video=81d80727f3022488598f68d323c1ad5e',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -38,7 +41,7 @@ class CloudflareStreamIE(InfoExtractor):
|
|||||||
return [
|
return [
|
||||||
mobj.group('url')
|
mobj.group('url')
|
||||||
for mobj in re.finditer(
|
for mobj in re.finditer(
|
||||||
r'<script[^>]+\bsrc=(["\'])(?P<url>(?:https?:)?//embed\.cloudflarestream\.com/embed/[^/]+\.js\?.*?\bvideo=[\da-f]+?.*?)\1',
|
r'<script[^>]+\bsrc=(["\'])(?P<url>(?:https?:)?//embed\.(?:cloudflarestream\.com|videodelivery\.net)/embed/[^/]+\.js\?.*?\bvideo=[\da-f]+?.*?)\1',
|
||||||
webpage)]
|
webpage)]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -67,6 +67,7 @@ from ..utils import (
|
|||||||
sanitized_Request,
|
sanitized_Request,
|
||||||
sanitize_filename,
|
sanitize_filename,
|
||||||
str_or_none,
|
str_or_none,
|
||||||
|
strip_or_none,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
@ -117,7 +118,7 @@ class InfoExtractor(object):
|
|||||||
unfragmented media)
|
unfragmented media)
|
||||||
- URL of the MPD manifest or base URL
|
- URL of the MPD manifest or base URL
|
||||||
representing the media if MPD manifest
|
representing the media if MPD manifest
|
||||||
is parsed froma string (in case of
|
is parsed from a string (in case of
|
||||||
fragmented media)
|
fragmented media)
|
||||||
for MSS - URL of the ISM manifest.
|
for MSS - URL of the ISM manifest.
|
||||||
* manifest_url
|
* manifest_url
|
||||||
@ -219,7 +220,7 @@ class InfoExtractor(object):
|
|||||||
* "preference" (optional, int) - quality of the image
|
* "preference" (optional, int) - quality of the image
|
||||||
* "width" (optional, int)
|
* "width" (optional, int)
|
||||||
* "height" (optional, int)
|
* "height" (optional, int)
|
||||||
* "resolution" (optional, string "{width}x{height"},
|
* "resolution" (optional, string "{width}x{height}",
|
||||||
deprecated)
|
deprecated)
|
||||||
* "filesize" (optional, int)
|
* "filesize" (optional, int)
|
||||||
thumbnail: Full URL to a video thumbnail image.
|
thumbnail: Full URL to a video thumbnail image.
|
||||||
@ -542,11 +543,11 @@ class InfoExtractor(object):
|
|||||||
raise ExtractorError('An extractor error has occurred.', cause=e)
|
raise ExtractorError('An extractor error has occurred.', cause=e)
|
||||||
|
|
||||||
def __maybe_fake_ip_and_retry(self, countries):
|
def __maybe_fake_ip_and_retry(self, countries):
|
||||||
if (not self._downloader.params.get('geo_bypass_country', None) and
|
if (not self._downloader.params.get('geo_bypass_country', None)
|
||||||
self._GEO_BYPASS and
|
and self._GEO_BYPASS
|
||||||
self._downloader.params.get('geo_bypass', True) and
|
and self._downloader.params.get('geo_bypass', True)
|
||||||
not self._x_forwarded_for_ip and
|
and not self._x_forwarded_for_ip
|
||||||
countries):
|
and countries):
|
||||||
country_code = random.choice(countries)
|
country_code = random.choice(countries)
|
||||||
self._x_forwarded_for_ip = GeoUtils.random_ipv4(country_code)
|
self._x_forwarded_for_ip = GeoUtils.random_ipv4(country_code)
|
||||||
if self._x_forwarded_for_ip:
|
if self._x_forwarded_for_ip:
|
||||||
@ -682,8 +683,8 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
def __check_blocked(self, content):
|
def __check_blocked(self, content):
|
||||||
first_block = content[:512]
|
first_block = content[:512]
|
||||||
if ('<title>Access to this site is blocked</title>' in content and
|
if ('<title>Access to this site is blocked</title>' in content
|
||||||
'Websense' in first_block):
|
and 'Websense' in first_block):
|
||||||
msg = 'Access to this webpage has been blocked by Websense filtering software in your network.'
|
msg = 'Access to this webpage has been blocked by Websense filtering software in your network.'
|
||||||
blocked_iframe = self._html_search_regex(
|
blocked_iframe = self._html_search_regex(
|
||||||
r'<iframe src="([^"]+)"', content,
|
r'<iframe src="([^"]+)"', content,
|
||||||
@ -701,8 +702,8 @@ class InfoExtractor(object):
|
|||||||
if block_msg:
|
if block_msg:
|
||||||
msg += ' (Message: "%s")' % block_msg.replace('\n', ' ')
|
msg += ' (Message: "%s")' % block_msg.replace('\n', ' ')
|
||||||
raise ExtractorError(msg, expected=True)
|
raise ExtractorError(msg, expected=True)
|
||||||
if ('<title>TTK :: Доступ к ресурсу ограничен</title>' in content and
|
if ('<title>TTK :: Доступ к ресурсу ограничен</title>' in content
|
||||||
'blocklist.rkn.gov.ru' in content):
|
and 'blocklist.rkn.gov.ru' in content):
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
'Access to this webpage has been blocked by decision of the Russian government. '
|
'Access to this webpage has been blocked by decision of the Russian government. '
|
||||||
'Visit http://blocklist.rkn.gov.ru/ for a block reason.',
|
'Visit http://blocklist.rkn.gov.ru/ for a block reason.',
|
||||||
@ -1709,8 +1710,8 @@ class InfoExtractor(object):
|
|||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
tbr = float_or_none(
|
tbr = float_or_none(
|
||||||
last_stream_inf.get('AVERAGE-BANDWIDTH') or
|
last_stream_inf.get('AVERAGE-BANDWIDTH')
|
||||||
last_stream_inf.get('BANDWIDTH'), scale=1000)
|
or last_stream_inf.get('BANDWIDTH'), scale=1000)
|
||||||
format_id = []
|
format_id = []
|
||||||
if m3u8_id:
|
if m3u8_id:
|
||||||
format_id.append(m3u8_id)
|
format_id.append(m3u8_id)
|
||||||
@ -2480,7 +2481,7 @@ class InfoExtractor(object):
|
|||||||
'subtitles': {},
|
'subtitles': {},
|
||||||
}
|
}
|
||||||
media_attributes = extract_attributes(media_tag)
|
media_attributes = extract_attributes(media_tag)
|
||||||
src = media_attributes.get('src')
|
src = strip_or_none(media_attributes.get('src'))
|
||||||
if src:
|
if src:
|
||||||
_, formats = _media_formats(src, media_type)
|
_, formats = _media_formats(src, media_type)
|
||||||
media_info['formats'].extend(formats)
|
media_info['formats'].extend(formats)
|
||||||
@ -2490,7 +2491,7 @@ class InfoExtractor(object):
|
|||||||
s_attr = extract_attributes(source_tag)
|
s_attr = extract_attributes(source_tag)
|
||||||
# data-video-src and data-src are non standard but seen
|
# data-video-src and data-src are non standard but seen
|
||||||
# several times in the wild
|
# several times in the wild
|
||||||
src = dict_get(s_attr, ('src', 'data-video-src', 'data-src'))
|
src = strip_or_none(dict_get(s_attr, ('src', 'data-video-src', 'data-src')))
|
||||||
if not src:
|
if not src:
|
||||||
continue
|
continue
|
||||||
f = parse_content_type(s_attr.get('type'))
|
f = parse_content_type(s_attr.get('type'))
|
||||||
@ -2504,8 +2505,8 @@ class InfoExtractor(object):
|
|||||||
if str_or_none(s_attr.get(lbl))
|
if str_or_none(s_attr.get(lbl))
|
||||||
]
|
]
|
||||||
width = int_or_none(s_attr.get('width'))
|
width = int_or_none(s_attr.get('width'))
|
||||||
height = (int_or_none(s_attr.get('height')) or
|
height = (int_or_none(s_attr.get('height'))
|
||||||
int_or_none(s_attr.get('res')))
|
or int_or_none(s_attr.get('res')))
|
||||||
if not width or not height:
|
if not width or not height:
|
||||||
for lbl in labels:
|
for lbl in labels:
|
||||||
resolution = parse_resolution(lbl)
|
resolution = parse_resolution(lbl)
|
||||||
@ -2533,7 +2534,7 @@ class InfoExtractor(object):
|
|||||||
track_attributes = extract_attributes(track_tag)
|
track_attributes = extract_attributes(track_tag)
|
||||||
kind = track_attributes.get('kind')
|
kind = track_attributes.get('kind')
|
||||||
if not kind or kind in ('subtitles', 'captions'):
|
if not kind or kind in ('subtitles', 'captions'):
|
||||||
src = track_attributes.get('src')
|
src = strip_or_none(track_attributes.get('src'))
|
||||||
if not src:
|
if not src:
|
||||||
continue
|
continue
|
||||||
lang = track_attributes.get('srclang') or track_attributes.get('lang') or track_attributes.get('label')
|
lang = track_attributes.get('srclang') or track_attributes.get('lang') or track_attributes.get('label')
|
||||||
@ -2817,6 +2818,33 @@ class InfoExtractor(object):
|
|||||||
self._downloader.cookiejar.add_cookie_header(req)
|
self._downloader.cookiejar.add_cookie_header(req)
|
||||||
return compat_cookies.SimpleCookie(req.get_header('Cookie'))
|
return compat_cookies.SimpleCookie(req.get_header('Cookie'))
|
||||||
|
|
||||||
|
def _apply_first_set_cookie_header(self, url_handle, cookie):
|
||||||
|
"""
|
||||||
|
Apply first Set-Cookie header instead of the last. Experimental.
|
||||||
|
|
||||||
|
Some sites (e.g. [1-3]) may serve two cookies under the same name
|
||||||
|
in Set-Cookie header and expect the first (old) one to be set rather
|
||||||
|
than second (new). However, as of RFC6265 the newer one cookie
|
||||||
|
should be set into cookie store what actually happens.
|
||||||
|
We will workaround this issue by resetting the cookie to
|
||||||
|
the first one manually.
|
||||||
|
1. https://new.vk.com/
|
||||||
|
2. https://github.com/ytdl-org/youtube-dl/issues/9841#issuecomment-227871201
|
||||||
|
3. https://learning.oreilly.com/
|
||||||
|
"""
|
||||||
|
for header, cookies in url_handle.headers.items():
|
||||||
|
if header.lower() != 'set-cookie':
|
||||||
|
continue
|
||||||
|
if sys.version_info[0] >= 3:
|
||||||
|
cookies = cookies.encode('iso-8859-1')
|
||||||
|
cookies = cookies.decode('utf-8')
|
||||||
|
cookie_value = re.search(
|
||||||
|
r'%s=(.+?);.*?\b[Dd]omain=(.+?)(?:[,;]|$)' % cookie, cookies)
|
||||||
|
if cookie_value:
|
||||||
|
value, domain = cookie_value.groups()
|
||||||
|
self._set_cookie(domain, cookie, value)
|
||||||
|
break
|
||||||
|
|
||||||
def get_testcases(self, include_onlymatching=False):
|
def get_testcases(self, include_onlymatching=False):
|
||||||
t = getattr(self, '_TEST', None)
|
t = getattr(self, '_TEST', None)
|
||||||
if t:
|
if t:
|
||||||
@ -2847,8 +2875,8 @@ class InfoExtractor(object):
|
|||||||
return not any_restricted
|
return not any_restricted
|
||||||
|
|
||||||
def extract_subtitles(self, *args, **kwargs):
|
def extract_subtitles(self, *args, **kwargs):
|
||||||
if (self._downloader.params.get('writesubtitles', False) or
|
if (self._downloader.params.get('writesubtitles', False)
|
||||||
self._downloader.params.get('listsubtitles')):
|
or self._downloader.params.get('listsubtitles')):
|
||||||
return self._get_subtitles(*args, **kwargs)
|
return self._get_subtitles(*args, **kwargs)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@ -2873,8 +2901,8 @@ class InfoExtractor(object):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def extract_automatic_captions(self, *args, **kwargs):
|
def extract_automatic_captions(self, *args, **kwargs):
|
||||||
if (self._downloader.params.get('writeautomaticsub', False) or
|
if (self._downloader.params.get('writeautomaticsub', False)
|
||||||
self._downloader.params.get('listsubtitles')):
|
or self._downloader.params.get('listsubtitles')):
|
||||||
return self._get_automatic_captions(*args, **kwargs)
|
return self._get_automatic_captions(*args, **kwargs)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@ -2882,9 +2910,9 @@ class InfoExtractor(object):
|
|||||||
raise NotImplementedError('This method must be implemented by subclasses')
|
raise NotImplementedError('This method must be implemented by subclasses')
|
||||||
|
|
||||||
def mark_watched(self, *args, **kwargs):
|
def mark_watched(self, *args, **kwargs):
|
||||||
if (self._downloader.params.get('mark_watched', False) and
|
if (self._downloader.params.get('mark_watched', False)
|
||||||
(self._get_login_info()[0] is not None or
|
and (self._get_login_info()[0] is not None
|
||||||
self._downloader.params.get('cookiefile') is not None)):
|
or self._downloader.params.get('cookiefile') is not None)):
|
||||||
self._mark_watched(*args, **kwargs)
|
self._mark_watched(*args, **kwargs)
|
||||||
|
|
||||||
def _mark_watched(self, *args, **kwargs):
|
def _mark_watched(self, *args, **kwargs):
|
||||||
|
@ -32,19 +32,19 @@ class CommonMistakesIE(InfoExtractor):
|
|||||||
|
|
||||||
|
|
||||||
class UnicodeBOMIE(InfoExtractor):
|
class UnicodeBOMIE(InfoExtractor):
|
||||||
IE_DESC = False
|
IE_DESC = False
|
||||||
_VALID_URL = r'(?P<bom>\ufeff)(?P<id>.*)$'
|
_VALID_URL = r'(?P<bom>\ufeff)(?P<id>.*)$'
|
||||||
|
|
||||||
# Disable test for python 3.2 since BOM is broken in re in this version
|
# Disable test for python 3.2 since BOM is broken in re in this version
|
||||||
# (see https://github.com/ytdl-org/youtube-dl/issues/9751)
|
# (see https://github.com/ytdl-org/youtube-dl/issues/9751)
|
||||||
_TESTS = [] if (3, 0) < sys.version_info <= (3, 3) else [{
|
_TESTS = [] if (3, 0) < sys.version_info <= (3, 3) else [{
|
||||||
'url': '\ufeffhttp://www.youtube.com/watch?v=BaW_jenozKc',
|
'url': '\ufeffhttp://www.youtube.com/watch?v=BaW_jenozKc',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
real_url = self._match_id(url)
|
real_url = self._match_id(url)
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
'Your URL starts with a Byte Order Mark (BOM). '
|
'Your URL starts with a Byte Order Mark (BOM). '
|
||||||
'Removing the BOM and looking for "%s" ...' % real_url)
|
'Removing the BOM and looking for "%s" ...' % real_url)
|
||||||
return self.url_result(real_url)
|
return self.url_result(real_url)
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
|
|
||||||
|
|
||||||
class CriterionIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?criterion\.com/films/(?P<id>[0-9]+)-.+'
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.criterion.com/films/184-le-samourai',
|
|
||||||
'md5': 'bc51beba55685509883a9a7830919ec3',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '184',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Le Samouraï',
|
|
||||||
'description': 'md5:a2b4b116326558149bef81f76dcbb93f',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
final_url = self._search_regex(
|
|
||||||
r'so\.addVariable\("videoURL", "(.+?)"\)\;', webpage, 'video url')
|
|
||||||
title = self._og_search_title(webpage)
|
|
||||||
description = self._html_search_meta('description', webpage)
|
|
||||||
thumbnail = self._search_regex(
|
|
||||||
r'so\.addVariable\("thumbnailURL", "(.+?)"\)\;',
|
|
||||||
webpage, 'thumbnail url')
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'url': final_url,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
}
|
|
@ -103,19 +103,6 @@ class CrunchyrollBaseIE(InfoExtractor):
|
|||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
self._login()
|
self._login()
|
||||||
|
|
||||||
def _download_webpage(self, url_or_request, *args, **kwargs):
|
|
||||||
request = (url_or_request if isinstance(url_or_request, compat_urllib_request.Request)
|
|
||||||
else sanitized_Request(url_or_request))
|
|
||||||
# Accept-Language must be set explicitly to accept any language to avoid issues
|
|
||||||
# similar to https://github.com/ytdl-org/youtube-dl/issues/6797.
|
|
||||||
# Along with IP address Crunchyroll uses Accept-Language to guess whether georestriction
|
|
||||||
# should be imposed or not (from what I can see it just takes the first language
|
|
||||||
# ignoring the priority and requires it to correspond the IP). By the way this causes
|
|
||||||
# Crunchyroll to not work in georestriction cases in some browsers that don't place
|
|
||||||
# the locale lang first in header. However allowing any language seems to workaround the issue.
|
|
||||||
request.add_header('Accept-Language', '*')
|
|
||||||
return super(CrunchyrollBaseIE, self)._download_webpage(request, *args, **kwargs)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _add_skip_wall(url):
|
def _add_skip_wall(url):
|
||||||
parsed_url = compat_urlparse.urlparse(url)
|
parsed_url = compat_urlparse.urlparse(url)
|
||||||
@ -269,6 +256,19 @@ class CrunchyrollIE(CrunchyrollBaseIE, VRVIE):
|
|||||||
'1080': ('80', '108'),
|
'1080': ('80', '108'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _download_webpage(self, url_or_request, *args, **kwargs):
|
||||||
|
request = (url_or_request if isinstance(url_or_request, compat_urllib_request.Request)
|
||||||
|
else sanitized_Request(url_or_request))
|
||||||
|
# Accept-Language must be set explicitly to accept any language to avoid issues
|
||||||
|
# similar to https://github.com/ytdl-org/youtube-dl/issues/6797.
|
||||||
|
# Along with IP address Crunchyroll uses Accept-Language to guess whether georestriction
|
||||||
|
# should be imposed or not (from what I can see it just takes the first language
|
||||||
|
# ignoring the priority and requires it to correspond the IP). By the way this causes
|
||||||
|
# Crunchyroll to not work in georestriction cases in some browsers that don't place
|
||||||
|
# the locale lang first in header. However allowing any language seems to workaround the issue.
|
||||||
|
request.add_header('Accept-Language', '*')
|
||||||
|
return super(CrunchyrollBaseIE, self)._download_webpage(request, *args, **kwargs)
|
||||||
|
|
||||||
def _decrypt_subtitles(self, data, iv, id):
|
def _decrypt_subtitles(self, data, iv, id):
|
||||||
data = bytes_to_intlist(compat_b64decode(data))
|
data = bytes_to_intlist(compat_b64decode(data))
|
||||||
iv = bytes_to_intlist(compat_b64decode(iv))
|
iv = bytes_to_intlist(compat_b64decode(iv))
|
||||||
@ -661,9 +661,8 @@ class CrunchyrollShowPlaylistIE(CrunchyrollBaseIE):
|
|||||||
webpage = self._download_webpage(
|
webpage = self._download_webpage(
|
||||||
self._add_skip_wall(url), show_id,
|
self._add_skip_wall(url), show_id,
|
||||||
headers=self.geo_verification_headers())
|
headers=self.geo_verification_headers())
|
||||||
title = self._html_search_regex(
|
title = self._html_search_meta('name', webpage, default=None)
|
||||||
r'(?s)<h1[^>]*>\s*<span itemprop="name">(.*?)</span>',
|
|
||||||
webpage, 'title')
|
|
||||||
episode_paths = re.findall(
|
episode_paths = re.findall(
|
||||||
r'(?s)<li id="showview_videos_media_(\d+)"[^>]+>.*?<a href="([^"]+)"',
|
r'(?s)<li id="showview_videos_media_(\d+)"[^>]+>.*?<a href="([^"]+)"',
|
||||||
webpage)
|
webpage)
|
||||||
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import unified_timestamp
|
from ..utils import unified_timestamp
|
||||||
|
from .youtube import YoutubeIE
|
||||||
|
|
||||||
|
|
||||||
class CtsNewsIE(InfoExtractor):
|
class CtsNewsIE(InfoExtractor):
|
||||||
@ -14,8 +15,8 @@ class CtsNewsIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '201501291578109',
|
'id': '201501291578109',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '以色列.真主黨交火 3人死亡',
|
'title': '以色列.真主黨交火 3人死亡 - 華視新聞網',
|
||||||
'description': '以色列和黎巴嫩真主黨,爆發五年最嚴重衝突,雙方砲轟交火,兩名以軍死亡,還有一名西班牙籍的聯合國維和人...',
|
'description': '以色列和黎巴嫩真主黨,爆發五年最嚴重衝突,雙方砲轟交火,兩名以軍死亡,還有一名西班牙籍的聯合國維和人員也不幸罹難。大陸陝西、河南、安徽、江蘇和湖北五個省份出現大暴雪,嚴重影響陸空交通,不過九華山卻出現...',
|
||||||
'timestamp': 1422528540,
|
'timestamp': 1422528540,
|
||||||
'upload_date': '20150129',
|
'upload_date': '20150129',
|
||||||
}
|
}
|
||||||
@ -26,7 +27,7 @@ class CtsNewsIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '201309031304098',
|
'id': '201309031304098',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '韓國31歲童顏男 貌如十多歲小孩',
|
'title': '韓國31歲童顏男 貌如十多歲小孩 - 華視新聞網',
|
||||||
'description': '越有年紀的人,越希望看起來年輕一點,而南韓卻有一位31歲的男子,看起來像是11、12歲的小孩,身...',
|
'description': '越有年紀的人,越希望看起來年輕一點,而南韓卻有一位31歲的男子,看起來像是11、12歲的小孩,身...',
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
'timestamp': 1378205880,
|
'timestamp': 1378205880,
|
||||||
@ -62,8 +63,7 @@ class CtsNewsIE(InfoExtractor):
|
|||||||
video_url = mp4_feed['source_url']
|
video_url = mp4_feed['source_url']
|
||||||
else:
|
else:
|
||||||
self.to_screen('Not CTSPlayer video, trying Youtube...')
|
self.to_screen('Not CTSPlayer video, trying Youtube...')
|
||||||
youtube_url = self._search_regex(
|
youtube_url = YoutubeIE._extract_url(page)
|
||||||
r'src="(//www\.youtube\.com/embed/[^"]+)"', page, 'youtube url')
|
|
||||||
|
|
||||||
return self.url_result(youtube_url, ie='Youtube')
|
return self.url_result(youtube_url, ie='Youtube')
|
||||||
|
|
||||||
|
@ -45,8 +45,8 @@ class DailyMailIE(InfoExtractor):
|
|||||||
sources_url = (try_get(
|
sources_url = (try_get(
|
||||||
video_data,
|
video_data,
|
||||||
(lambda x: x['plugins']['sources']['url'],
|
(lambda x: x['plugins']['sources']['url'],
|
||||||
lambda x: x['sources']['url']), compat_str) or
|
lambda x: x['sources']['url']), compat_str)
|
||||||
'http://www.dailymail.co.uk/api/player/%s/video-sources.json' % video_id)
|
or 'http://www.dailymail.co.uk/api/player/%s/video-sources.json' % video_id)
|
||||||
|
|
||||||
video_sources = self._download_json(sources_url, video_id)
|
video_sources = self._download_json(sources_url, video_id)
|
||||||
body = video_sources.get('body')
|
body = video_sources.get('body')
|
||||||
|
@ -137,10 +137,16 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_urls(webpage):
|
def _extract_urls(webpage):
|
||||||
|
urls = []
|
||||||
# Look for embedded Dailymotion player
|
# Look for embedded Dailymotion player
|
||||||
matches = re.findall(
|
# https://developer.dailymotion.com/player#player-parameters
|
||||||
r'<(?:(?:embed|iframe)[^>]+?src=|input[^>]+id=[\'"]dmcloudUrlEmissionSelect[\'"][^>]+value=)(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.com/(?:embed|swf)/video/.+?)\1', webpage)
|
for mobj in re.finditer(
|
||||||
return list(map(lambda m: unescapeHTML(m[1]), matches))
|
r'<(?:(?:embed|iframe)[^>]+?src=|input[^>]+id=[\'"]dmcloudUrlEmissionSelect[\'"][^>]+value=)(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.com/(?:embed|swf)/video/.+?)\1', webpage):
|
||||||
|
urls.append(unescapeHTML(mobj.group('url')))
|
||||||
|
for mobj in re.finditer(
|
||||||
|
r'(?s)DM\.player\([^,]+,\s*{.*?video[\'"]?\s*:\s*["\']?(?P<id>[0-9a-zA-Z]+).+?}\s*\);', webpage):
|
||||||
|
urls.append('https://www.dailymotion.com/embed/video/' + mobj.group('id'))
|
||||||
|
return urls
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
@ -7,50 +7,51 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
|
|
||||||
class DBTVIE(InfoExtractor):
|
class DBTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?dbtv\.no/(?:[^/]+/)?(?P<id>[0-9]+)(?:#(?P<display_id>.+))?'
|
_VALID_URL = r'https?://(?:www\.)?dagbladet\.no/video/(?:(?:embed|(?P<display_id>[^/]+))/)?(?P<id>[0-9A-Za-z_-]{11}|[a-zA-Z0-9]{8})'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://dbtv.no/3649835190001#Skulle_teste_ut_fornøyelsespark,_men_kollegaen_var_bare_opptatt_av_bikinikroppen',
|
'url': 'https://www.dagbladet.no/video/PynxJnNWChE/',
|
||||||
'md5': '2e24f67936517b143a234b4cadf792ec',
|
'md5': 'b8f850ba1860adbda668d367f9b77699',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '3649835190001',
|
'id': 'PynxJnNWChE',
|
||||||
'display_id': 'Skulle_teste_ut_fornøyelsespark,_men_kollegaen_var_bare_opptatt_av_bikinikroppen',
|
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Skulle teste ut fornøyelsespark, men kollegaen var bare opptatt av bikinikroppen',
|
'title': 'Skulle teste ut fornøyelsespark, men kollegaen var bare opptatt av bikinikroppen',
|
||||||
'description': 'md5:1504a54606c4dde3e4e61fc97aa857e0',
|
'description': 'md5:49cc8370e7d66e8a2ef15c3b4631fd3f',
|
||||||
'thumbnail': r're:https?://.*\.jpg',
|
'thumbnail': r're:https?://.*\.jpg',
|
||||||
'timestamp': 1404039863,
|
'upload_date': '20160916',
|
||||||
'upload_date': '20140629',
|
'duration': 69,
|
||||||
'duration': 69.544,
|
'uploader_id': 'UCk5pvsyZJoYJBd7_oFPTlRQ',
|
||||||
'uploader_id': '1027729757001',
|
'uploader': 'Dagbladet',
|
||||||
},
|
},
|
||||||
'add_ie': ['BrightcoveNew']
|
'add_ie': ['Youtube']
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://dbtv.no/3649835190001',
|
'url': 'https://www.dagbladet.no/video/embed/xlGmyIeN9Jo/?autoplay=false',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.dbtv.no/lazyplayer/4631135248001',
|
'url': 'https://www.dagbladet.no/video/truer-iran-bor-passe-dere/PalfB2Cw',
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://dbtv.no/vice/5000634109001',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://dbtv.no/filmtrailer/3359293614001',
|
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_urls(webpage):
|
def _extract_urls(webpage):
|
||||||
return [url for _, url in re.findall(
|
return [url for _, url in re.findall(
|
||||||
r'<iframe[^>]+src=(["\'])((?:https?:)?//(?:www\.)?dbtv\.no/(?:lazy)?player/\d+.*?)\1',
|
r'<iframe[^>]+src=(["\'])((?:https?:)?//(?:www\.)?dagbladet\.no/video/embed/(?:[0-9A-Za-z_-]{11}|[a-zA-Z0-9]{8}).*?)\1',
|
||||||
webpage)]
|
webpage)]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id, display_id = re.match(self._VALID_URL, url).groups()
|
display_id, video_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
info = {
|
||||||
return {
|
|
||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
'url': 'http://players.brightcove.net/1027729757001/default_default/index.html?videoId=%s' % video_id,
|
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'ie_key': 'BrightcoveNew',
|
|
||||||
}
|
}
|
||||||
|
if len(video_id) == 11:
|
||||||
|
info.update({
|
||||||
|
'url': video_id,
|
||||||
|
'ie_key': 'Youtube',
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
info.update({
|
||||||
|
'url': 'jwplatform:' + video_id,
|
||||||
|
'ie_key': 'JWPlatform',
|
||||||
|
})
|
||||||
|
return info
|
||||||
|
@ -70,8 +70,8 @@ class DctpTvIE(InfoExtractor):
|
|||||||
endpoint = next(
|
endpoint = next(
|
||||||
server['endpoint']
|
server['endpoint']
|
||||||
for server in servers
|
for server in servers
|
||||||
if url_or_none(server.get('endpoint')) and
|
if url_or_none(server.get('endpoint'))
|
||||||
'cloudfront' in server['endpoint'])
|
and 'cloudfront' in server['endpoint'])
|
||||||
else:
|
else:
|
||||||
endpoint = 'rtmpe://s2pqqn4u96e4j8.cloudfront.net/cfx/st/'
|
endpoint = 'rtmpe://s2pqqn4u96e4j8.cloudfront.net/cfx/st/'
|
||||||
|
|
||||||
|
@ -19,9 +19,9 @@ from ..compat import compat_HTTPError
|
|||||||
class DiscoveryIE(DiscoveryGoBaseIE):
|
class DiscoveryIE(DiscoveryGoBaseIE):
|
||||||
_VALID_URL = r'''(?x)https?://
|
_VALID_URL = r'''(?x)https?://
|
||||||
(?P<site>
|
(?P<site>
|
||||||
|
(?:(?:www|go)\.)?discovery|
|
||||||
(?:www\.)?
|
(?:www\.)?
|
||||||
(?:
|
(?:
|
||||||
discovery|
|
|
||||||
investigationdiscovery|
|
investigationdiscovery|
|
||||||
discoverylife|
|
discoverylife|
|
||||||
animalplanet|
|
animalplanet|
|
||||||
@ -56,6 +56,9 @@ class DiscoveryIE(DiscoveryGoBaseIE):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://www.investigationdiscovery.com/tv-shows/final-vision/full-episodes/final-vision',
|
'url': 'https://www.investigationdiscovery.com/tv-shows/final-vision/full-episodes/final-vision',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://go.discovery.com/tv-shows/alaskan-bush-people/videos/follow-your-own-road',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
_GEO_COUNTRIES = ['US']
|
_GEO_COUNTRIES = ['US']
|
||||||
_GEO_BYPASS = False
|
_GEO_BYPASS = False
|
||||||
|
97
youtube_dl/extractor/dlive.py
Normal file
97
youtube_dl/extractor/dlive.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class DLiveVODIE(InfoExtractor):
|
||||||
|
IE_NAME = 'dlive:vod'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?dlive\.tv/p/(?P<uploader_id>.+?)\+(?P<id>[^/?#&]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://dlive.tv/p/pdp+3mTzOl4WR',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '3mTzOl4WR',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Minecraft with james charles epic',
|
||||||
|
'upload_date': '20190701',
|
||||||
|
'timestamp': 1562011015,
|
||||||
|
'uploader_id': 'pdp',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://dlive.tv/p/pdpreplay+D-RD-xSZg',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
uploader_id, vod_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
broadcast = self._download_json(
|
||||||
|
'https://graphigo.prd.dlive.tv/', vod_id,
|
||||||
|
data=json.dumps({'query': '''query {
|
||||||
|
pastBroadcast(permlink:"%s+%s") {
|
||||||
|
content
|
||||||
|
createdAt
|
||||||
|
length
|
||||||
|
playbackUrl
|
||||||
|
title
|
||||||
|
thumbnailUrl
|
||||||
|
viewCount
|
||||||
|
}
|
||||||
|
}''' % (uploader_id, vod_id)}).encode())['data']['pastBroadcast']
|
||||||
|
title = broadcast['title']
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
broadcast['playbackUrl'], vod_id, 'mp4', 'm3u8_native')
|
||||||
|
self._sort_formats(formats)
|
||||||
|
return {
|
||||||
|
'id': vod_id,
|
||||||
|
'title': title,
|
||||||
|
'uploader_id': uploader_id,
|
||||||
|
'formats': formats,
|
||||||
|
'description': broadcast.get('content'),
|
||||||
|
'thumbnail': broadcast.get('thumbnailUrl'),
|
||||||
|
'timestamp': int_or_none(broadcast.get('createdAt'), 1000),
|
||||||
|
'view_count': int_or_none(broadcast.get('viewCount')),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DLiveStreamIE(InfoExtractor):
|
||||||
|
IE_NAME = 'dlive:stream'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?dlive\.tv/(?!p/)(?P<id>[\w.-]+)'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_name = self._match_id(url)
|
||||||
|
user = self._download_json(
|
||||||
|
'https://graphigo.prd.dlive.tv/', display_name,
|
||||||
|
data=json.dumps({'query': '''query {
|
||||||
|
userByDisplayName(displayname:"%s") {
|
||||||
|
livestream {
|
||||||
|
content
|
||||||
|
createdAt
|
||||||
|
title
|
||||||
|
thumbnailUrl
|
||||||
|
watchingCount
|
||||||
|
}
|
||||||
|
username
|
||||||
|
}
|
||||||
|
}''' % display_name}).encode())['data']['userByDisplayName']
|
||||||
|
livestream = user['livestream']
|
||||||
|
title = livestream['title']
|
||||||
|
username = user['username']
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
'https://live.prd.dlive.tv/hls/live/%s.m3u8' % username,
|
||||||
|
display_name, 'mp4')
|
||||||
|
self._sort_formats(formats)
|
||||||
|
return {
|
||||||
|
'id': display_name,
|
||||||
|
'title': self._live_title(title),
|
||||||
|
'uploader': display_name,
|
||||||
|
'uploader_id': username,
|
||||||
|
'formats': formats,
|
||||||
|
'description': livestream.get('content'),
|
||||||
|
'thumbnail': livestream.get('thumbnailUrl'),
|
||||||
|
'is_live': True,
|
||||||
|
'timestamp': int_or_none(livestream.get('createdAt'), 1000),
|
||||||
|
'view_count': int_or_none(livestream.get('watchingCount')),
|
||||||
|
}
|
@ -24,7 +24,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class DRTVIE(InfoExtractor):
|
class DRTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?dr\.dk/(?:tv/se|nyheder|radio/ondemand)/(?:[^/]+/)*(?P<id>[\da-z-]+)(?:[/#?]|$)'
|
_VALID_URL = r'https?://(?:www\.)?dr\.dk/(?:tv/se|nyheder|radio(?:/ondemand)?)/(?:[^/]+/)*(?P<id>[\da-z-]+)(?:[/#?]|$)'
|
||||||
_GEO_BYPASS = False
|
_GEO_BYPASS = False
|
||||||
_GEO_COUNTRIES = ['DK']
|
_GEO_COUNTRIES = ['DK']
|
||||||
IE_NAME = 'drtv'
|
IE_NAME = 'drtv'
|
||||||
@ -80,6 +80,9 @@ class DRTVIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.dr.dk/radio/p4kbh/regionale-nyheder-kh4/p4-nyheder-2019-06-26-17-30-9',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
@ -18,7 +19,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class EinthusanIE(InfoExtractor):
|
class EinthusanIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://einthusan\.tv/movie/watch/(?P<id>[^/?#&]+)'
|
_VALID_URL = r'https?://(?P<host>einthusan\.(?:tv|com))/movie/watch/(?P<id>[^/?#&]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://einthusan.tv/movie/watch/9097/',
|
'url': 'https://einthusan.tv/movie/watch/9097/',
|
||||||
'md5': 'ff0f7f2065031b8a2cf13a933731c035',
|
'md5': 'ff0f7f2065031b8a2cf13a933731c035',
|
||||||
@ -32,6 +33,9 @@ class EinthusanIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://einthusan.tv/movie/watch/51MZ/?lang=hindi',
|
'url': 'https://einthusan.tv/movie/watch/51MZ/?lang=hindi',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://einthusan.com/movie/watch/9097/',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
# reversed from jsoncrypto.prototype.decrypt() in einthusan-PGMovieWatcher.js
|
# reversed from jsoncrypto.prototype.decrypt() in einthusan-PGMovieWatcher.js
|
||||||
@ -41,7 +45,9 @@ class EinthusanIE(InfoExtractor):
|
|||||||
)).decode('utf-8'), video_id)
|
)).decode('utf-8'), video_id)
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
host = mobj.group('host')
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
@ -53,7 +59,7 @@ class EinthusanIE(InfoExtractor):
|
|||||||
page_id = self._html_search_regex(
|
page_id = self._html_search_regex(
|
||||||
'<html[^>]+data-pageid="([^"]+)"', webpage, 'page ID')
|
'<html[^>]+data-pageid="([^"]+)"', webpage, 'page ID')
|
||||||
video_data = self._download_json(
|
video_data = self._download_json(
|
||||||
'https://einthusan.tv/ajax/movie/watch/%s/' % video_id, video_id,
|
'https://%s/ajax/movie/watch/%s/' % (host, video_id), video_id,
|
||||||
data=urlencode_postdata({
|
data=urlencode_postdata({
|
||||||
'xEvent': 'UIVideoPlayer.PingOutcome',
|
'xEvent': 'UIVideoPlayer.PingOutcome',
|
||||||
'xJson': json.dumps({
|
'xJson': json.dumps({
|
||||||
|
@ -216,17 +216,14 @@ class FiveThirtyEightIE(InfoExtractor):
|
|||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://fivethirtyeight.com/features/how-the-6-8-raiders-can-still-make-the-playoffs/',
|
'url': 'http://fivethirtyeight.com/features/how-the-6-8-raiders-can-still-make-the-playoffs/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '21846851',
|
'id': '56032156',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'title': 'FiveThirtyEight: The Raiders can still make the playoffs',
|
'title': 'FiveThirtyEight: The Raiders can still make the playoffs',
|
||||||
'description': 'Neil Paine breaks down the simplest scenario that will put the Raiders into the playoffs at 8-8.',
|
'description': 'Neil Paine breaks down the simplest scenario that will put the Raiders into the playoffs at 8-8.',
|
||||||
'timestamp': 1513960621,
|
|
||||||
'upload_date': '20171222',
|
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
'expected_warnings': ['Unable to download f4m manifest'],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -234,9 +231,8 @@ class FiveThirtyEightIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_id = self._search_regex(
|
embed_url = self._search_regex(
|
||||||
r'data-video-id=["\'](?P<id>\d+)',
|
r'<iframe[^>]+src=["\'](https?://fivethirtyeight\.abcnews\.go\.com/video/embed/\d+/\d+)',
|
||||||
webpage, 'video id', group='id')
|
webpage, 'embed url')
|
||||||
|
|
||||||
return self.url_result(
|
return self.url_result(embed_url, 'AbcNewsVideo')
|
||||||
'http://espn.go.com/video/clip?id=%s' % video_id, ESPNIE.ie_key())
|
|
||||||
|
@ -82,8 +82,8 @@ class ExpressenIE(InfoExtractor):
|
|||||||
title = info.get('titleRaw') or data['title']
|
title = info.get('titleRaw') or data['title']
|
||||||
description = info.get('descriptionRaw')
|
description = info.get('descriptionRaw')
|
||||||
thumbnail = info.get('socialMediaImage') or data.get('image')
|
thumbnail = info.get('socialMediaImage') or data.get('image')
|
||||||
duration = int_or_none(info.get('videoTotalSecondsDuration') or
|
duration = int_or_none(info.get('videoTotalSecondsDuration')
|
||||||
data.get('totalSecondsDuration'))
|
or data.get('totalSecondsDuration'))
|
||||||
timestamp = unified_timestamp(info.get('publishDate'))
|
timestamp = unified_timestamp(info.get('publishDate'))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -58,17 +58,8 @@ from .ard import (
|
|||||||
ARDMediathekIE,
|
ARDMediathekIE,
|
||||||
)
|
)
|
||||||
from .arte import (
|
from .arte import (
|
||||||
ArteTvIE,
|
|
||||||
ArteTVPlus7IE,
|
ArteTVPlus7IE,
|
||||||
ArteTVCreativeIE,
|
|
||||||
ArteTVConcertIE,
|
|
||||||
ArteTVInfoIE,
|
|
||||||
ArteTVFutureIE,
|
|
||||||
ArteTVCinemaIE,
|
|
||||||
ArteTVDDCIE,
|
|
||||||
ArteTVMagazineIE,
|
|
||||||
ArteTVEmbedIE,
|
ArteTVEmbedIE,
|
||||||
TheOperaPlatformIE,
|
|
||||||
ArteTVPlaylistIE,
|
ArteTVPlaylistIE,
|
||||||
)
|
)
|
||||||
from .asiancrush import (
|
from .asiancrush import (
|
||||||
@ -173,6 +164,7 @@ from .cbs import CBSIE
|
|||||||
from .cbslocal import CBSLocalIE
|
from .cbslocal import CBSLocalIE
|
||||||
from .cbsinteractive import CBSInteractiveIE
|
from .cbsinteractive import CBSInteractiveIE
|
||||||
from .cbsnews import (
|
from .cbsnews import (
|
||||||
|
CBSNewsEmbedIE,
|
||||||
CBSNewsIE,
|
CBSNewsIE,
|
||||||
CBSNewsLiveVideoIE,
|
CBSNewsLiveVideoIE,
|
||||||
)
|
)
|
||||||
@ -240,7 +232,6 @@ from .condenast import CondeNastIE
|
|||||||
from .corus import CorusIE
|
from .corus import CorusIE
|
||||||
from .cracked import CrackedIE
|
from .cracked import CrackedIE
|
||||||
from .crackle import CrackleIE
|
from .crackle import CrackleIE
|
||||||
from .criterion import CriterionIE
|
|
||||||
from .crooksandliars import CrooksAndLiarsIE
|
from .crooksandliars import CrooksAndLiarsIE
|
||||||
from .crunchyroll import (
|
from .crunchyroll import (
|
||||||
CrunchyrollIE,
|
CrunchyrollIE,
|
||||||
@ -408,11 +399,7 @@ from .frontendmasters import (
|
|||||||
FrontendMastersCourseIE
|
FrontendMastersCourseIE
|
||||||
)
|
)
|
||||||
from .funimation import FunimationIE
|
from .funimation import FunimationIE
|
||||||
from .funk import (
|
from .funk import FunkIE
|
||||||
FunkMixIE,
|
|
||||||
FunkChannelIE,
|
|
||||||
)
|
|
||||||
from .funnyordie import FunnyOrDieIE
|
|
||||||
from .fusion import FusionIE
|
from .fusion import FusionIE
|
||||||
from .fxnetworks import FXNetworksIE
|
from .fxnetworks import FXNetworksIE
|
||||||
from .gaia import GaiaIE
|
from .gaia import GaiaIE
|
||||||
@ -596,6 +583,7 @@ from .linkedin import (
|
|||||||
)
|
)
|
||||||
from .linuxacademy import LinuxAcademyIE
|
from .linuxacademy import LinuxAcademyIE
|
||||||
from .litv import LiTVIE
|
from .litv import LiTVIE
|
||||||
|
from .livejournal import LiveJournalIE
|
||||||
from .liveleak import (
|
from .liveleak import (
|
||||||
LiveLeakIE,
|
LiveLeakIE,
|
||||||
LiveLeakEmbedIE,
|
LiveLeakEmbedIE,
|
||||||
@ -749,7 +737,6 @@ from .nexx import (
|
|||||||
NexxIE,
|
NexxIE,
|
||||||
NexxEmbedIE,
|
NexxEmbedIE,
|
||||||
)
|
)
|
||||||
from .nfb import NFBIE
|
|
||||||
from .nfl import NFLIE
|
from .nfl import NFLIE
|
||||||
from .nhk import NhkVodIE
|
from .nhk import NhkVodIE
|
||||||
from .nhl import NHLIE
|
from .nhl import NHLIE
|
||||||
@ -776,13 +763,6 @@ from .nova import (
|
|||||||
NovaEmbedIE,
|
NovaEmbedIE,
|
||||||
NovaIE,
|
NovaIE,
|
||||||
)
|
)
|
||||||
from .novamov import (
|
|
||||||
AuroraVidIE,
|
|
||||||
CloudTimeIE,
|
|
||||||
NowVideoIE,
|
|
||||||
VideoWeedIE,
|
|
||||||
WholeCloudIE,
|
|
||||||
)
|
|
||||||
from .nowness import (
|
from .nowness import (
|
||||||
NownessIE,
|
NownessIE,
|
||||||
NownessPlaylistIE,
|
NownessPlaylistIE,
|
||||||
@ -837,7 +817,10 @@ from .ooyala import (
|
|||||||
OoyalaIE,
|
OoyalaIE,
|
||||||
OoyalaExternalIE,
|
OoyalaExternalIE,
|
||||||
)
|
)
|
||||||
from .openload import OpenloadIE
|
from .openload import (
|
||||||
|
OpenloadIE,
|
||||||
|
VerystreamIE,
|
||||||
|
)
|
||||||
from .ora import OraTVIE
|
from .ora import OraTVIE
|
||||||
from .orf import (
|
from .orf import (
|
||||||
ORFTVthekIE,
|
ORFTVthekIE,
|
||||||
@ -897,12 +880,12 @@ from .polskieradio import (
|
|||||||
from .popcorntv import PopcornTVIE
|
from .popcorntv import PopcornTVIE
|
||||||
from .porn91 import Porn91IE
|
from .porn91 import Porn91IE
|
||||||
from .porncom import PornComIE
|
from .porncom import PornComIE
|
||||||
from .pornflip import PornFlipIE
|
|
||||||
from .pornhd import PornHdIE
|
from .pornhd import PornHdIE
|
||||||
from .pornhub import (
|
from .pornhub import (
|
||||||
PornHubIE,
|
PornHubIE,
|
||||||
PornHubPlaylistIE,
|
PornHubUserIE,
|
||||||
PornHubUserVideosIE,
|
PornHubPagedVideoListIE,
|
||||||
|
PornHubUserVideosUploadIE,
|
||||||
)
|
)
|
||||||
from .pornotube import PornotubeIE
|
from .pornotube import PornotubeIE
|
||||||
from .pornovoisines import PornoVoisinesIE
|
from .pornovoisines import PornoVoisinesIE
|
||||||
@ -947,7 +930,10 @@ from .raywenderlich import (
|
|||||||
)
|
)
|
||||||
from .rbmaradio import RBMARadioIE
|
from .rbmaradio import RBMARadioIE
|
||||||
from .rds import RDSIE
|
from .rds import RDSIE
|
||||||
from .redbulltv import RedBullTVIE
|
from .redbulltv import (
|
||||||
|
RedBullTVIE,
|
||||||
|
RedBullTVRrnContentIE,
|
||||||
|
)
|
||||||
from .reddit import (
|
from .reddit import (
|
||||||
RedditIE,
|
RedditIE,
|
||||||
RedditRIE,
|
RedditRIE,
|
||||||
@ -986,7 +972,6 @@ from .rts import RTSIE
|
|||||||
from .rtve import RTVEALaCartaIE, RTVELiveIE, RTVEInfantilIE, RTVELiveIE, RTVETelevisionIE
|
from .rtve import RTVEALaCartaIE, RTVELiveIE, RTVEInfantilIE, RTVELiveIE, RTVETelevisionIE
|
||||||
from .rtvnh import RTVNHIE
|
from .rtvnh import RTVNHIE
|
||||||
from .rtvs import RTVSIE
|
from .rtvs import RTVSIE
|
||||||
from .rudo import RudoIE
|
|
||||||
from .ruhd import RUHDIE
|
from .ruhd import RUHDIE
|
||||||
from .rutube import (
|
from .rutube import (
|
||||||
RutubeIE,
|
RutubeIE,
|
||||||
@ -1274,6 +1259,10 @@ from .udn import UDNEmbedIE
|
|||||||
from .ufctv import UFCTVIE
|
from .ufctv import UFCTVIE
|
||||||
from .uktvplay import UKTVPlayIE
|
from .uktvplay import UKTVPlayIE
|
||||||
from .digiteka import DigitekaIE
|
from .digiteka import DigitekaIE
|
||||||
|
from .dlive import (
|
||||||
|
DLiveVODIE,
|
||||||
|
DLiveStreamIE,
|
||||||
|
)
|
||||||
from .umg import UMGDeIE
|
from .umg import UMGDeIE
|
||||||
from .unistra import UnistraIE
|
from .unistra import UnistraIE
|
||||||
from .unity import UnityIE
|
from .unity import UnityIE
|
||||||
@ -1463,6 +1452,7 @@ from .yahoo import (
|
|||||||
YahooSearchIE,
|
YahooSearchIE,
|
||||||
YahooGyaOPlayerIE,
|
YahooGyaOPlayerIE,
|
||||||
YahooGyaOIE,
|
YahooGyaOIE,
|
||||||
|
YahooJapanNewsIE,
|
||||||
)
|
)
|
||||||
from .yandexdisk import YandexDiskIE
|
from .yandexdisk import YandexDiskIE
|
||||||
from .yandexmusic import (
|
from .yandexmusic import (
|
||||||
|
@ -428,7 +428,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
timestamp = int_or_none(self._search_regex(
|
timestamp = int_or_none(self._search_regex(
|
||||||
r'<abbr[^>]+data-utime=["\'](\d+)', webpage,
|
r'<abbr[^>]+data-utime=["\'](\d+)', webpage,
|
||||||
'timestamp', default=None))
|
'timestamp', default=None))
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
thumbnail = self._html_search_meta(['og:image', 'twitter:image'], webpage)
|
||||||
|
|
||||||
view_count = parse_count(self._search_regex(
|
view_count = parse_count(self._search_regex(
|
||||||
r'\bviewCount\s*:\s*["\']([\d,.]+)', webpage, 'view count',
|
r'\bviewCount\s*:\s*["\']([\d,.]+)', webpage, 'view count',
|
||||||
|
@ -9,7 +9,7 @@ from ..utils import int_or_none
|
|||||||
|
|
||||||
class FiveTVIE(InfoExtractor):
|
class FiveTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
http://
|
https?://
|
||||||
(?:www\.)?5-tv\.ru/
|
(?:www\.)?5-tv\.ru/
|
||||||
(?:
|
(?:
|
||||||
(?:[^/]+/)+(?P<id>\d+)|
|
(?:[^/]+/)+(?P<id>\d+)|
|
||||||
@ -39,6 +39,7 @@ class FiveTVIE(InfoExtractor):
|
|||||||
'duration': 180,
|
'duration': 180,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
|
# redirect to https://www.5-tv.ru/projects/1000095/izvestia-glavnoe/
|
||||||
'url': 'http://www.5-tv.ru/glavnoe/#itemDetails',
|
'url': 'http://www.5-tv.ru/glavnoe/#itemDetails',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'glavnoe',
|
'id': 'glavnoe',
|
||||||
@ -46,6 +47,7 @@ class FiveTVIE(InfoExtractor):
|
|||||||
'title': r're:^Итоги недели с \d+ по \d+ \w+ \d{4} года$',
|
'title': r're:^Итоги недели с \d+ по \d+ \w+ \d{4} года$',
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
},
|
},
|
||||||
|
'skip': 'redirect to «Известия. Главное» project page',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.5-tv.ru/glavnoe/broadcasts/508645/',
|
'url': 'http://www.5-tv.ru/glavnoe/broadcasts/508645/',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@ -70,7 +72,7 @@ class FiveTVIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_url = self._search_regex(
|
video_url = self._search_regex(
|
||||||
[r'<div[^>]+?class="flowplayer[^>]+?data-href="([^"]+)"',
|
[r'<div[^>]+?class="(?:flow)?player[^>]+?data-href="([^"]+)"',
|
||||||
r'<a[^>]+?href="([^"]+)"[^>]+?class="videoplayer"'],
|
r'<a[^>]+?href="([^"]+)"[^>]+?class="videoplayer"'],
|
||||||
webpage, 'video url')
|
webpage, 'video url')
|
||||||
|
|
||||||
|
@ -94,8 +94,8 @@ class FrontendMastersPageBaseIE(FrontendMastersBaseIE):
|
|||||||
chapter_number = None
|
chapter_number = None
|
||||||
index = lesson.get('index')
|
index = lesson.get('index')
|
||||||
element_index = lesson.get('elementIndex')
|
element_index = lesson.get('elementIndex')
|
||||||
if (isinstance(index, int) and isinstance(element_index, int) and
|
if (isinstance(index, int) and isinstance(element_index, int)
|
||||||
index < element_index):
|
and index < element_index):
|
||||||
chapter_number = element_index - index
|
chapter_number = element_index - index
|
||||||
chapter = (chapters[chapter_number - 1]
|
chapter = (chapters[chapter_number - 1]
|
||||||
if chapter_number - 1 < len(chapters) else None)
|
if chapter_number - 1 < len(chapters) else None)
|
||||||
|
@ -1,89 +1,21 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import itertools
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .nexx import NexxIE
|
from .nexx import NexxIE
|
||||||
from ..compat import compat_str
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
try_get,
|
str_or_none,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FunkBaseIE(InfoExtractor):
|
class FunkIE(InfoExtractor):
|
||||||
_HEADERS = {
|
_VALID_URL = r'https?://(?:www\.)?funk\.net/(?:channel|playlist)/[^/]+/(?P<display_id>[0-9a-z-]+)-(?P<id>\d+)'
|
||||||
'Accept': '*/*',
|
|
||||||
'Accept-Language': 'en-US,en;q=0.9,ru;q=0.8',
|
|
||||||
'authorization': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnROYW1lIjoid2ViYXBwLXYzMSIsInNjb3BlIjoic3RhdGljLWNvbnRlbnQtYXBpLGN1cmF0aW9uLWFwaSxuZXh4LWNvbnRlbnQtYXBpLXYzMSx3ZWJhcHAtYXBpIn0.mbuG9wS9Yf5q6PqgR4fiaRFIagiHk9JhwoKES7ksVX4',
|
|
||||||
}
|
|
||||||
_AUTH = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnROYW1lIjoid2ViYXBwLXYzMSIsInNjb3BlIjoic3RhdGljLWNvbnRlbnQtYXBpLGN1cmF0aW9uLWFwaSxuZXh4LWNvbnRlbnQtYXBpLXYzMSx3ZWJhcHAtYXBpIn0.mbuG9wS9Yf5q6PqgR4fiaRFIagiHk9JhwoKES7ksVX4'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _make_headers(referer):
|
|
||||||
headers = FunkBaseIE._HEADERS.copy()
|
|
||||||
headers['Referer'] = referer
|
|
||||||
return headers
|
|
||||||
|
|
||||||
def _make_url_result(self, video):
|
|
||||||
return {
|
|
||||||
'_type': 'url_transparent',
|
|
||||||
'url': 'nexx:741:%s' % video['sourceId'],
|
|
||||||
'ie_key': NexxIE.ie_key(),
|
|
||||||
'id': video['sourceId'],
|
|
||||||
'title': video.get('title'),
|
|
||||||
'description': video.get('description'),
|
|
||||||
'duration': int_or_none(video.get('duration')),
|
|
||||||
'season_number': int_or_none(video.get('seasonNr')),
|
|
||||||
'episode_number': int_or_none(video.get('episodeNr')),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class FunkMixIE(FunkBaseIE):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?funk\.net/mix/(?P<id>[^/]+)/(?P<alias>[^/?#&]+)'
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://www.funk.net/mix/59d65d935f8b160001828b5b/die-realste-kifferdoku-aller-zeiten',
|
'url': 'https://www.funk.net/channel/ba-793/die-lustigsten-instrumente-aus-dem-internet-teil-2-1155821',
|
||||||
'md5': '8edf617c2f2b7c9847dfda313f199009',
|
'md5': '8dd9d9ab59b4aa4173b3197f2ea48e81',
|
||||||
'info_dict': {
|
|
||||||
'id': '123748',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': '"Die realste Kifferdoku aller Zeiten"',
|
|
||||||
'description': 'md5:c97160f5bafa8d47ec8e2e461012aa9d',
|
|
||||||
'timestamp': 1490274721,
|
|
||||||
'upload_date': '20170323',
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
mix_id = mobj.group('id')
|
|
||||||
alias = mobj.group('alias')
|
|
||||||
|
|
||||||
lists = self._download_json(
|
|
||||||
'https://www.funk.net/api/v3.1/curation/curatedLists/',
|
|
||||||
mix_id, headers=self._make_headers(url), query={
|
|
||||||
'size': 100,
|
|
||||||
})['_embedded']['curatedListList']
|
|
||||||
|
|
||||||
metas = next(
|
|
||||||
l for l in lists
|
|
||||||
if mix_id in (l.get('entityId'), l.get('alias')))['videoMetas']
|
|
||||||
video = next(
|
|
||||||
meta['videoDataDelegate']
|
|
||||||
for meta in metas
|
|
||||||
if try_get(
|
|
||||||
meta, lambda x: x['videoDataDelegate']['alias'],
|
|
||||||
compat_str) == alias)
|
|
||||||
|
|
||||||
return self._make_url_result(video)
|
|
||||||
|
|
||||||
|
|
||||||
class FunkChannelIE(FunkBaseIE):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?funk\.net/channel/(?P<id>[^/]+)/(?P<alias>[^/?#&]+)'
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'https://www.funk.net/channel/ba/die-lustigsten-instrumente-aus-dem-internet-teil-2',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1155821',
|
'id': '1155821',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@ -92,83 +24,26 @@ class FunkChannelIE(FunkBaseIE):
|
|||||||
'timestamp': 1514507395,
|
'timestamp': 1514507395,
|
||||||
'upload_date': '20171229',
|
'upload_date': '20171229',
|
||||||
},
|
},
|
||||||
'params': {
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
}, {
|
}, {
|
||||||
# only available via byIdList API
|
'url': 'https://www.funk.net/playlist/neuesteVideos/kameras-auf-dem-fusion-festival-1618699',
|
||||||
'url': 'https://www.funk.net/channel/informr/martin-sonneborn-erklaert-die-eu',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '205067',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Martin Sonneborn erklärt die EU',
|
|
||||||
'description': 'md5:050f74626e4ed87edf4626d2024210c0',
|
|
||||||
'timestamp': 1494424042,
|
|
||||||
'upload_date': '20170510',
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'url': 'https://www.funk.net/channel/59d5149841dca100012511e3/mein-erster-job-lovemilla-folge-1/lovemilla/',
|
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
display_id, nexx_id = re.match(self._VALID_URL, url).groups()
|
||||||
channel_id = mobj.group('id')
|
video = self._download_json(
|
||||||
alias = mobj.group('alias')
|
'https://www.funk.net/api/v4.0/videos/' + nexx_id, nexx_id)
|
||||||
|
return {
|
||||||
headers = self._make_headers(url)
|
'_type': 'url_transparent',
|
||||||
|
'url': 'nexx:741:' + nexx_id,
|
||||||
video = None
|
'ie_key': NexxIE.ie_key(),
|
||||||
|
'id': nexx_id,
|
||||||
# Id-based channels are currently broken on their side: webplayer
|
'title': video.get('title'),
|
||||||
# tries to process them via byChannelAlias endpoint and fails
|
'description': video.get('description'),
|
||||||
# predictably.
|
'duration': int_or_none(video.get('duration')),
|
||||||
for page_num in itertools.count():
|
'channel_id': str_or_none(video.get('channelId')),
|
||||||
by_channel_alias = self._download_json(
|
'display_id': display_id,
|
||||||
'https://www.funk.net/api/v3.1/webapp/videos/byChannelAlias/%s'
|
'tags': video.get('tags'),
|
||||||
% channel_id,
|
'thumbnail': video.get('imageUrlLandscape'),
|
||||||
'Downloading byChannelAlias JSON page %d' % (page_num + 1),
|
}
|
||||||
headers=headers, query={
|
|
||||||
'filterFsk': 'false',
|
|
||||||
'sort': 'creationDate,desc',
|
|
||||||
'size': 100,
|
|
||||||
'page': page_num,
|
|
||||||
}, fatal=False)
|
|
||||||
if not by_channel_alias:
|
|
||||||
break
|
|
||||||
video_list = try_get(
|
|
||||||
by_channel_alias, lambda x: x['_embedded']['videoList'], list)
|
|
||||||
if not video_list:
|
|
||||||
break
|
|
||||||
try:
|
|
||||||
video = next(r for r in video_list if r.get('alias') == alias)
|
|
||||||
break
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
if not try_get(
|
|
||||||
by_channel_alias, lambda x: x['_links']['next']):
|
|
||||||
break
|
|
||||||
|
|
||||||
if not video:
|
|
||||||
by_id_list = self._download_json(
|
|
||||||
'https://www.funk.net/api/v3.0/content/videos/byIdList',
|
|
||||||
channel_id, 'Downloading byIdList JSON', headers=headers,
|
|
||||||
query={
|
|
||||||
'ids': alias,
|
|
||||||
}, fatal=False)
|
|
||||||
if by_id_list:
|
|
||||||
video = try_get(by_id_list, lambda x: x['result'][0], dict)
|
|
||||||
|
|
||||||
if not video:
|
|
||||||
results = self._download_json(
|
|
||||||
'https://www.funk.net/api/v3.0/content/videos/filter',
|
|
||||||
channel_id, 'Downloading filter JSON', headers=headers, query={
|
|
||||||
'channelId': channel_id,
|
|
||||||
'size': 100,
|
|
||||||
})['result']
|
|
||||||
video = next(r for r in results if r.get('alias') == alias)
|
|
||||||
|
|
||||||
return self._make_url_result(video)
|
|
||||||
|
@ -1,162 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..utils import (
|
|
||||||
ExtractorError,
|
|
||||||
float_or_none,
|
|
||||||
int_or_none,
|
|
||||||
unified_timestamp,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FunnyOrDieIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?funnyordie\.com/(?P<type>embed|articles|videos)/(?P<id>[0-9a-f]+)(?:$|[?#/])'
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://www.funnyordie.com/videos/0732f586d7/heart-shaped-box-literal-video-version',
|
|
||||||
'md5': 'bcd81e0c4f26189ee09be362ad6e6ba9',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '0732f586d7',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Heart-Shaped Box: Literal Video Version',
|
|
||||||
'description': 'md5:ea09a01bc9a1c46d9ab696c01747c338',
|
|
||||||
'thumbnail': r're:^http:.*\.jpg$',
|
|
||||||
'uploader': 'DASjr',
|
|
||||||
'timestamp': 1317904928,
|
|
||||||
'upload_date': '20111006',
|
|
||||||
'duration': 318.3,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.funnyordie.com/embed/e402820827',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'e402820827',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Please Use This Song (Jon Lajoie)',
|
|
||||||
'description': 'Please use this to sell something. www.jonlajoie.com',
|
|
||||||
'thumbnail': r're:^http:.*\.jpg$',
|
|
||||||
'timestamp': 1398988800,
|
|
||||||
'upload_date': '20140502',
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.funnyordie.com/articles/ebf5e34fc8/10-hours-of-walking-in-nyc-as-a-man',
|
|
||||||
'only_matching': True,
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
|
|
||||||
video_id = mobj.group('id')
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
links = re.findall(r'<source src="([^"]+/v)[^"]+\.([^"]+)" type=\'video', webpage)
|
|
||||||
if not links:
|
|
||||||
raise ExtractorError('No media links available for %s' % video_id)
|
|
||||||
|
|
||||||
links.sort(key=lambda link: 1 if link[1] == 'mp4' else 0)
|
|
||||||
|
|
||||||
m3u8_url = self._search_regex(
|
|
||||||
r'<source[^>]+src=(["\'])(?P<url>.+?/master\.m3u8[^"\']*)\1',
|
|
||||||
webpage, 'm3u8 url', group='url')
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
|
|
||||||
m3u8_formats = self._extract_m3u8_formats(
|
|
||||||
m3u8_url, video_id, 'mp4', 'm3u8_native',
|
|
||||||
m3u8_id='hls', fatal=False)
|
|
||||||
source_formats = list(filter(
|
|
||||||
lambda f: f.get('vcodec') != 'none', m3u8_formats))
|
|
||||||
|
|
||||||
bitrates = [int(bitrate) for bitrate in re.findall(r'[,/]v(\d+)(?=[,/])', m3u8_url)]
|
|
||||||
bitrates.sort()
|
|
||||||
|
|
||||||
if source_formats:
|
|
||||||
self._sort_formats(source_formats)
|
|
||||||
|
|
||||||
for bitrate, f in zip(bitrates, source_formats or [{}] * len(bitrates)):
|
|
||||||
for path, ext in links:
|
|
||||||
ff = f.copy()
|
|
||||||
if ff:
|
|
||||||
if ext != 'mp4':
|
|
||||||
ff = dict(
|
|
||||||
[(k, v) for k, v in ff.items()
|
|
||||||
if k in ('height', 'width', 'format_id')])
|
|
||||||
ff.update({
|
|
||||||
'format_id': ff['format_id'].replace('hls', ext),
|
|
||||||
'ext': ext,
|
|
||||||
'protocol': 'http',
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
ff.update({
|
|
||||||
'format_id': '%s-%d' % (ext, bitrate),
|
|
||||||
'vbr': bitrate,
|
|
||||||
})
|
|
||||||
ff['url'] = self._proto_relative_url(
|
|
||||||
'%s%d.%s' % (path, bitrate, ext))
|
|
||||||
formats.append(ff)
|
|
||||||
self._check_formats(formats, video_id)
|
|
||||||
|
|
||||||
formats.extend(m3u8_formats)
|
|
||||||
self._sort_formats(
|
|
||||||
formats, field_preference=('height', 'width', 'tbr', 'format_id'))
|
|
||||||
|
|
||||||
subtitles = {}
|
|
||||||
for src, src_lang in re.findall(r'<track kind="captions" src="([^"]+)" srclang="([^"]+)"', webpage):
|
|
||||||
subtitles[src_lang] = [{
|
|
||||||
'ext': src.split('/')[-1],
|
|
||||||
'url': 'http://www.funnyordie.com%s' % src,
|
|
||||||
}]
|
|
||||||
|
|
||||||
timestamp = unified_timestamp(self._html_search_meta(
|
|
||||||
'uploadDate', webpage, 'timestamp', default=None))
|
|
||||||
|
|
||||||
uploader = self._html_search_regex(
|
|
||||||
r'<h\d[^>]+\bclass=["\']channel-preview-name[^>]+>(.+?)</h',
|
|
||||||
webpage, 'uploader', default=None)
|
|
||||||
|
|
||||||
title, description, thumbnail, duration = [None] * 4
|
|
||||||
|
|
||||||
medium = self._parse_json(
|
|
||||||
self._search_regex(
|
|
||||||
r'jsonMedium\s*=\s*({.+?});', webpage, 'JSON medium',
|
|
||||||
default='{}'),
|
|
||||||
video_id, fatal=False)
|
|
||||||
if medium:
|
|
||||||
title = medium.get('title')
|
|
||||||
duration = float_or_none(medium.get('duration'))
|
|
||||||
if not timestamp:
|
|
||||||
timestamp = unified_timestamp(medium.get('publishDate'))
|
|
||||||
|
|
||||||
post = self._parse_json(
|
|
||||||
self._search_regex(
|
|
||||||
r'fb_post\s*=\s*(\{.*?\});', webpage, 'post details',
|
|
||||||
default='{}'),
|
|
||||||
video_id, fatal=False)
|
|
||||||
if post:
|
|
||||||
if not title:
|
|
||||||
title = post.get('name')
|
|
||||||
description = post.get('description')
|
|
||||||
thumbnail = post.get('picture')
|
|
||||||
|
|
||||||
if not title:
|
|
||||||
title = self._og_search_title(webpage)
|
|
||||||
if not description:
|
|
||||||
description = self._og_search_description(webpage)
|
|
||||||
if not duration:
|
|
||||||
duration = int_or_none(self._html_search_meta(
|
|
||||||
('video:duration', 'duration'), webpage, 'duration', default=False))
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'uploader': uploader,
|
|
||||||
'timestamp': timestamp,
|
|
||||||
'duration': duration,
|
|
||||||
'formats': formats,
|
|
||||||
'subtitles': subtitles,
|
|
||||||
}
|
|
@ -1,35 +1,84 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .ooyala import OoyalaIE
|
from ..utils import (
|
||||||
|
determine_ext,
|
||||||
|
int_or_none,
|
||||||
|
mimetype2ext,
|
||||||
|
parse_iso8601,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FusionIE(InfoExtractor):
|
class FusionIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?fusion\.(?:net|tv)/video/(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?fusion\.(?:net|tv)/(?:video/|show/.+?\bvideo=)(?P<id>\d+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://fusion.tv/video/201781/u-s-and-panamanian-forces-work-together-to-stop-a-vessel-smuggling-drugs/',
|
'url': 'http://fusion.tv/video/201781/u-s-and-panamanian-forces-work-together-to-stop-a-vessel-smuggling-drugs/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'ZpcWNoMTE6x6uVIIWYpHh0qQDjxBuq5P',
|
'id': '3145868',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'U.S. and Panamanian forces work together to stop a vessel smuggling drugs',
|
'title': 'U.S. and Panamanian forces work together to stop a vessel smuggling drugs',
|
||||||
'description': 'md5:0cc84a9943c064c0f46b128b41b1b0d7',
|
'description': 'md5:0cc84a9943c064c0f46b128b41b1b0d7',
|
||||||
'duration': 140.0,
|
'duration': 140.0,
|
||||||
|
'timestamp': 1442589635,
|
||||||
|
'uploader': 'UNIVISON',
|
||||||
|
'upload_date': '20150918',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
'add_ie': ['Ooyala'],
|
'add_ie': ['Anvato'],
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://fusion.tv/video/201781',
|
'url': 'http://fusion.tv/video/201781',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://fusion.tv/show/food-exposed-with-nelufar-hedayat/?ancla=full-episodes&video=588644',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
display_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, display_id)
|
video = self._download_json(
|
||||||
|
'https://platform.fusion.net/wp-json/fusiondotnet/v1/video/' + video_id, video_id)
|
||||||
|
|
||||||
ooyala_code = self._search_regex(
|
info = {
|
||||||
r'data-ooyala-id=(["\'])(?P<code>(?:(?!\1).)+)\1',
|
'id': video_id,
|
||||||
webpage, 'ooyala code', group='code')
|
'title': video['title'],
|
||||||
|
'description': video.get('excerpt'),
|
||||||
|
'timestamp': parse_iso8601(video.get('published')),
|
||||||
|
'series': video.get('show'),
|
||||||
|
}
|
||||||
|
|
||||||
return OoyalaIE._build_url_result(ooyala_code)
|
formats = []
|
||||||
|
src = video.get('src') or {}
|
||||||
|
for f_id, f in src.items():
|
||||||
|
for q_id, q in f.items():
|
||||||
|
q_url = q.get('url')
|
||||||
|
if not q_url:
|
||||||
|
continue
|
||||||
|
ext = determine_ext(q_url, mimetype2ext(q.get('type')))
|
||||||
|
if ext == 'smil':
|
||||||
|
formats.extend(self._extract_smil_formats(q_url, video_id, fatal=False))
|
||||||
|
elif f_id == 'm3u8-variant' or (ext == 'm3u8' and q_id == 'Variant'):
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
q_url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
|
||||||
|
else:
|
||||||
|
formats.append({
|
||||||
|
'format_id': '-'.join([f_id, q_id]),
|
||||||
|
'url': q_url,
|
||||||
|
'width': int_or_none(q.get('width')),
|
||||||
|
'height': int_or_none(q.get('height')),
|
||||||
|
'tbr': int_or_none(self._search_regex(r'_(\d+)\.m(?:p4|3u8)', q_url, 'bitrate')),
|
||||||
|
'ext': 'mp4' if ext == 'm3u8' else ext,
|
||||||
|
'protocol': 'm3u8_native' if ext == 'm3u8' else 'https',
|
||||||
|
})
|
||||||
|
if formats:
|
||||||
|
self._sort_formats(formats)
|
||||||
|
info['formats'] = formats
|
||||||
|
else:
|
||||||
|
info.update({
|
||||||
|
'_type': 'url',
|
||||||
|
'url': 'anvato:uni:' + video['video_ids']['anvato'],
|
||||||
|
'ie_key': 'Anvato',
|
||||||
|
})
|
||||||
|
|
||||||
|
return info
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .brightcove import BrightcoveNewIE
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
clean_html,
|
||||||
|
get_element_by_class,
|
||||||
|
get_element_by_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GameInformerIE(InfoExtractor):
|
class GameInformerIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?gameinformer\.com/(?:[^/]+/)*(?P<id>.+)\.aspx'
|
_VALID_URL = r'https?://(?:www\.)?gameinformer\.com/(?:[^/]+/)*(?P<id>[^.?&#]+)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
|
# normal Brightcove embed code extracted with BrightcoveNewIE._extract_url
|
||||||
'url': 'http://www.gameinformer.com/b/features/archive/2015/09/26/replay-animal-crossing.aspx',
|
'url': 'http://www.gameinformer.com/b/features/archive/2015/09/26/replay-animal-crossing.aspx',
|
||||||
'md5': '292f26da1ab4beb4c9099f1304d2b071',
|
'md5': '292f26da1ab4beb4c9099f1304d2b071',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -18,16 +25,25 @@ class GameInformerIE(InfoExtractor):
|
|||||||
'upload_date': '20150928',
|
'upload_date': '20150928',
|
||||||
'uploader_id': '694940074001',
|
'uploader_id': '694940074001',
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
# Brightcove id inside unique element with field--name-field-brightcove-video-id class
|
||||||
|
'url': 'https://www.gameinformer.com/video-feature/new-gameplay-today/2019/07/09/new-gameplay-today-streets-of-rogue',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6057111913001',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'New Gameplay Today – Streets Of Rogue',
|
||||||
|
'timestamp': 1562699001,
|
||||||
|
'upload_date': '20190709',
|
||||||
|
'uploader_id': '694940074001',
|
||||||
|
|
||||||
|
},
|
||||||
|
}]
|
||||||
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/694940074001/default_default/index.html?videoId=%s'
|
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/694940074001/default_default/index.html?videoId=%s'
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
display_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(
|
webpage = self._download_webpage(
|
||||||
url, display_id, headers=self.geo_verification_headers())
|
url, display_id, headers=self.geo_verification_headers())
|
||||||
brightcove_id = self._search_regex(
|
brightcove_id = clean_html(get_element_by_class('field--name-field-brightcove-video-id', webpage) or get_element_by_id('video-source-content', webpage))
|
||||||
[r'<[^>]+\bid=["\']bc_(\d+)', r"getVideo\('[^']+video_id=(\d+)"],
|
brightcove_url = self.BRIGHTCOVE_URL_TEMPLATE % brightcove_id if brightcove_id else BrightcoveNewIE._extract_url(self, webpage)
|
||||||
webpage, 'brightcove id')
|
return self.url_result(brightcove_url, 'BrightcoveNew', brightcove_id)
|
||||||
return self.url_result(
|
|
||||||
self.BRIGHTCOVE_URL_TEMPLATE % brightcove_id, 'BrightcoveNew',
|
|
||||||
brightcove_id)
|
|
||||||
|
@ -89,7 +89,10 @@ from .piksel import PikselIE
|
|||||||
from .videa import VideaIE
|
from .videa import VideaIE
|
||||||
from .twentymin import TwentyMinutenIE
|
from .twentymin import TwentyMinutenIE
|
||||||
from .ustream import UstreamIE
|
from .ustream import UstreamIE
|
||||||
from .openload import OpenloadIE
|
from .openload import (
|
||||||
|
OpenloadIE,
|
||||||
|
VerystreamIE,
|
||||||
|
)
|
||||||
from .videopress import VideoPressIE
|
from .videopress import VideoPressIE
|
||||||
from .rutube import RutubeIE
|
from .rutube import RutubeIE
|
||||||
from .limelight import LimelightBaseIE
|
from .limelight import LimelightBaseIE
|
||||||
@ -2101,6 +2104,23 @@ class GenericIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'expected_warnings': ['Failed to download MPD manifest'],
|
'expected_warnings': ['Failed to download MPD manifest'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
# DailyMotion embed with DM.player
|
||||||
|
'url': 'https://www.beinsports.com/us/copa-del-rey/video/the-locker-room-valencia-beat-barca-in-copa/1203804',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'k6aKkGHd9FJs4mtJN39',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'The Locker Room: Valencia Beat Barca In Copa del Rey Final',
|
||||||
|
'description': 'This video is private.',
|
||||||
|
'uploader_id': 'x1jf30l',
|
||||||
|
'uploader': 'beIN SPORTS USA',
|
||||||
|
'upload_date': '20190528',
|
||||||
|
'timestamp': 1559062971,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
# {
|
# {
|
||||||
# # TODO: find another test
|
# # TODO: find another test
|
||||||
# # http://schema.org/VideoObject
|
# # http://schema.org/VideoObject
|
||||||
@ -2206,7 +2226,7 @@ class GenericIE(InfoExtractor):
|
|||||||
default_search = 'fixup_error'
|
default_search = 'fixup_error'
|
||||||
|
|
||||||
if default_search in ('auto', 'auto_warning', 'fixup_error'):
|
if default_search in ('auto', 'auto_warning', 'fixup_error'):
|
||||||
if '/' in url:
|
if re.match(r'^[^\s/]+\.[^\s/]+/', url):
|
||||||
self._downloader.report_warning('The url doesn\'t specify the protocol, trying with http')
|
self._downloader.report_warning('The url doesn\'t specify the protocol, trying with http')
|
||||||
return self.url_result('http://' + url)
|
return self.url_result('http://' + url)
|
||||||
elif default_search != 'fixup_error':
|
elif default_search != 'fixup_error':
|
||||||
@ -2546,11 +2566,11 @@ class GenericIE(InfoExtractor):
|
|||||||
return self.url_result(mobj.group('url'))
|
return self.url_result(mobj.group('url'))
|
||||||
|
|
||||||
# Look for Ooyala videos
|
# Look for Ooyala videos
|
||||||
mobj = (re.search(r'player\.ooyala\.com/[^"?]+[?#][^"]*?(?:embedCode|ec)=(?P<ec>[^"&]+)', webpage) or
|
mobj = (re.search(r'player\.ooyala\.com/[^"?]+[?#][^"]*?(?:embedCode|ec)=(?P<ec>[^"&]+)', webpage)
|
||||||
re.search(r'OO\.Player\.create\([\'"].*?[\'"],\s*[\'"](?P<ec>.{32})[\'"]', webpage) or
|
or re.search(r'OO\.Player\.create\([\'"].*?[\'"],\s*[\'"](?P<ec>.{32})[\'"]', webpage)
|
||||||
re.search(r'OO\.Player\.create\.apply\(\s*OO\.Player\s*,\s*op\(\s*\[\s*[\'"][^\'"]*[\'"]\s*,\s*[\'"](?P<ec>.{32})[\'"]', webpage) or
|
or re.search(r'OO\.Player\.create\.apply\(\s*OO\.Player\s*,\s*op\(\s*\[\s*[\'"][^\'"]*[\'"]\s*,\s*[\'"](?P<ec>.{32})[\'"]', webpage)
|
||||||
re.search(r'SBN\.VideoLinkset\.ooyala\([\'"](?P<ec>.{32})[\'"]\)', webpage) or
|
or re.search(r'SBN\.VideoLinkset\.ooyala\([\'"](?P<ec>.{32})[\'"]\)', webpage)
|
||||||
re.search(r'data-ooyala-video-id\s*=\s*[\'"](?P<ec>.{32})[\'"]', webpage))
|
or re.search(r'data-ooyala-video-id\s*=\s*[\'"](?P<ec>.{32})[\'"]', webpage))
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
embed_token = self._search_regex(
|
embed_token = self._search_regex(
|
||||||
r'embedToken[\'"]?\s*:\s*[\'"]([^\'"]+)',
|
r'embedToken[\'"]?\s*:\s*[\'"]([^\'"]+)',
|
||||||
@ -2580,19 +2600,6 @@ class GenericIE(InfoExtractor):
|
|||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result(mobj.group(1), 'Mpora')
|
return self.url_result(mobj.group(1), 'Mpora')
|
||||||
|
|
||||||
# Look for embedded NovaMov-based player
|
|
||||||
mobj = re.search(
|
|
||||||
r'''(?x)<(?:pagespeed_)?iframe[^>]+?src=(["\'])
|
|
||||||
(?P<url>http://(?:(?:embed|www)\.)?
|
|
||||||
(?:novamov\.com|
|
|
||||||
nowvideo\.(?:ch|sx|eu|at|ag|co)|
|
|
||||||
videoweed\.(?:es|com)|
|
|
||||||
movshare\.(?:net|sx|ag)|
|
|
||||||
divxstage\.(?:eu|net|ch|co|at|ag))
|
|
||||||
/embed\.php.+?)\1''', webpage)
|
|
||||||
if mobj is not None:
|
|
||||||
return self.url_result(mobj.group('url'))
|
|
||||||
|
|
||||||
# Look for embedded Facebook player
|
# Look for embedded Facebook player
|
||||||
facebook_urls = FacebookIE._extract_urls(webpage)
|
facebook_urls = FacebookIE._extract_urls(webpage)
|
||||||
if facebook_urls:
|
if facebook_urls:
|
||||||
@ -3017,6 +3024,12 @@ class GenericIE(InfoExtractor):
|
|||||||
return self.playlist_from_matches(
|
return self.playlist_from_matches(
|
||||||
openload_urls, video_id, video_title, ie=OpenloadIE.ie_key())
|
openload_urls, video_id, video_title, ie=OpenloadIE.ie_key())
|
||||||
|
|
||||||
|
# Look for Verystream embeds
|
||||||
|
verystream_urls = VerystreamIE._extract_urls(webpage)
|
||||||
|
if verystream_urls:
|
||||||
|
return self.playlist_from_matches(
|
||||||
|
verystream_urls, video_id, video_title, ie=VerystreamIE.ie_key())
|
||||||
|
|
||||||
# Look for VideoPress embeds
|
# Look for VideoPress embeds
|
||||||
videopress_urls = VideoPressIE._extract_urls(webpage)
|
videopress_urls = VideoPressIE._extract_urls(webpage)
|
||||||
if videopress_urls:
|
if videopress_urls:
|
||||||
@ -3212,8 +3225,8 @@ class GenericIE(InfoExtractor):
|
|||||||
else:
|
else:
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': src,
|
'url': src,
|
||||||
'ext': (mimetype2ext(src_type) or
|
'ext': (mimetype2ext(src_type)
|
||||||
ext if ext in KNOWN_EXTENSIONS else 'mp4'),
|
or ext if ext in KNOWN_EXTENSIONS else 'mp4'),
|
||||||
})
|
})
|
||||||
if formats:
|
if formats:
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
@ -11,7 +11,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class GfycatIE(InfoExtractor):
|
class GfycatIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?gfycat\.com/(?:ifr/|gifs/detail/)?(?P<id>[^/?#]+)'
|
_VALID_URL = r'https?://(?:www\.)?gfycat\.com/(?:ru/|ifr/|gifs/detail/)?(?P<id>[^-/?#]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://gfycat.com/DeadlyDecisiveGermanpinscher',
|
'url': 'http://gfycat.com/DeadlyDecisiveGermanpinscher',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -44,9 +44,15 @@ class GfycatIE(InfoExtractor):
|
|||||||
'categories': list,
|
'categories': list,
|
||||||
'age_limit': 0,
|
'age_limit': 0,
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://gfycat.com/ru/RemarkableDrearyAmurstarfish',
|
||||||
|
'only_matching': True
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://gfycat.com/gifs/detail/UnconsciousLankyIvorygull',
|
'url': 'https://gfycat.com/gifs/detail/UnconsciousLankyIvorygull',
|
||||||
'only_matching': True
|
'only_matching': True
|
||||||
|
}, {
|
||||||
|
'url': 'https://gfycat.com/acceptablehappygoluckyharborporpoise-baseball',
|
||||||
|
'only_matching': True
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -34,9 +34,13 @@ class GoIE(AdobePassIE):
|
|||||||
'watchdisneyxd': {
|
'watchdisneyxd': {
|
||||||
'brand': '009',
|
'brand': '009',
|
||||||
'resource_id': 'DisneyXD',
|
'resource_id': 'DisneyXD',
|
||||||
|
},
|
||||||
|
'disneynow': {
|
||||||
|
'brand': '011',
|
||||||
|
'resource_id': 'Disney',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_VALID_URL = r'https?://(?:(?P<sub_domain>%s)\.)?go\.com/(?:(?:[^/]+/)*(?P<id>vdka\w+)|(?:[^/]+/)*(?P<display_id>[^/?#]+))'\
|
_VALID_URL = r'https?://(?:(?:(?P<sub_domain>%s)\.)?go|(?P<sub_domain_2>disneynow))\.com/(?:(?:[^/]+/)*(?P<id>vdka\w+)|(?:[^/]+/)*(?P<display_id>[^/?#]+))'\
|
||||||
% '|'.join(list(_SITE_INFO.keys()) + ['disneynow'])
|
% '|'.join(list(_SITE_INFO.keys()) + ['disneynow'])
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://abc.go.com/shows/designated-survivor/video/most-recent/VDKA3807643',
|
'url': 'http://abc.go.com/shows/designated-survivor/video/most-recent/VDKA3807643',
|
||||||
@ -71,6 +75,9 @@ class GoIE(AdobePassIE):
|
|||||||
# brand 008
|
# brand 008
|
||||||
'url': 'http://disneynow.go.com/shows/minnies-bow-toons/video/happy-campers/vdka4872013',
|
'url': 'http://disneynow.go.com/shows/minnies-bow-toons/video/happy-campers/vdka4872013',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://disneynow.com/shows/minnies-bow-toons/video/happy-campers/vdka4872013',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _extract_videos(self, brand, video_id='-1', show_id='-1'):
|
def _extract_videos(self, brand, video_id='-1', show_id='-1'):
|
||||||
@ -80,7 +87,9 @@ class GoIE(AdobePassIE):
|
|||||||
display_id)['video']
|
display_id)['video']
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
sub_domain, video_id, display_id = re.match(self._VALID_URL, url).groups()
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
sub_domain = mobj.group('sub_domain') or mobj.group('sub_domain_2')
|
||||||
|
video_id, display_id = mobj.group('id', 'display_id')
|
||||||
site_info = self._SITE_INFO.get(sub_domain, {})
|
site_info = self._SITE_INFO.get(sub_domain, {})
|
||||||
brand = site_info.get('brand')
|
brand = site_info.get('brand')
|
||||||
if not video_id or not site_info:
|
if not video_id or not site_info:
|
||||||
@ -89,7 +98,7 @@ class GoIE(AdobePassIE):
|
|||||||
# There may be inner quotes, e.g. data-video-id="'VDKA3609139'"
|
# There may be inner quotes, e.g. data-video-id="'VDKA3609139'"
|
||||||
# from http://freeform.go.com/shows/shadowhunters/episodes/season-2/1-this-guilty-blood
|
# from http://freeform.go.com/shows/shadowhunters/episodes/season-2/1-this-guilty-blood
|
||||||
r'data-video-id=["\']*(VDKA\w+)', webpage, 'video id',
|
r'data-video-id=["\']*(VDKA\w+)', webpage, 'video id',
|
||||||
default=None)
|
default=video_id)
|
||||||
if not site_info:
|
if not site_info:
|
||||||
brand = self._search_regex(
|
brand = self._search_regex(
|
||||||
(r'data-brand=\s*["\']\s*(\d+)',
|
(r'data-brand=\s*["\']\s*(\d+)',
|
||||||
|
@ -155,8 +155,8 @@ class HeiseIE(InfoExtractor):
|
|||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': description,
|
'description': description,
|
||||||
'thumbnail': (xpath_text(doc, './/{http://rss.jwpcdn.com/}image') or
|
'thumbnail': (xpath_text(doc, './/{http://rss.jwpcdn.com/}image')
|
||||||
self._og_search_thumbnail(webpage)),
|
or self._og_search_thumbnail(webpage)),
|
||||||
'timestamp': parse_iso8601(
|
'timestamp': parse_iso8601(
|
||||||
self._html_search_meta('date', webpage)),
|
self._html_search_meta('date', webpage)),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
|
@ -58,8 +58,8 @@ class HitboxIE(InfoExtractor):
|
|||||||
title = video_meta.get('media_status')
|
title = video_meta.get('media_status')
|
||||||
alt_title = video_meta.get('media_title')
|
alt_title = video_meta.get('media_title')
|
||||||
description = clean_html(
|
description = clean_html(
|
||||||
video_meta.get('media_description') or
|
video_meta.get('media_description')
|
||||||
video_meta.get('media_description_md'))
|
or video_meta.get('media_description_md'))
|
||||||
duration = float_or_none(video_meta.get('media_duration'))
|
duration = float_or_none(video_meta.get('media_duration'))
|
||||||
uploader = video_meta.get('media_user_name')
|
uploader = video_meta.get('media_user_name')
|
||||||
views = int_or_none(video_meta.get('media_views'))
|
views = int_or_none(video_meta.get('media_views'))
|
||||||
|
@ -47,8 +47,8 @@ class HitRecordIE(InfoExtractor):
|
|||||||
tags = [
|
tags = [
|
||||||
t['text']
|
t['text']
|
||||||
for t in tags_list
|
for t in tags_list
|
||||||
if isinstance(t, dict) and t.get('text') and
|
if isinstance(t, dict) and t.get('text')
|
||||||
isinstance(t['text'], compat_str)]
|
and isinstance(t['text'], compat_str)]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
@ -77,13 +77,13 @@ class HKETVIE(InfoExtractor):
|
|||||||
|
|
||||||
title = (
|
title = (
|
||||||
self._html_search_meta(
|
self._html_search_meta(
|
||||||
('ed_title', 'search.ed_title'), webpage, default=None) or
|
('ed_title', 'search.ed_title'), webpage, default=None)
|
||||||
self._search_regex(
|
or self._search_regex(
|
||||||
r'data-favorite_title_(?:eng|chi)=(["\'])(?P<id>(?:(?!\1).)+)\1',
|
r'data-favorite_title_(?:eng|chi)=(["\'])(?P<id>(?:(?!\1).)+)\1',
|
||||||
webpage, 'title', default=None, group='url') or
|
webpage, 'title', default=None, group='url')
|
||||||
self._html_search_regex(
|
or self._html_search_regex(
|
||||||
r'<h1>([^<]+)</h1>', webpage, 'title', default=None) or
|
r'<h1>([^<]+)</h1>', webpage, 'title', default=None)
|
||||||
self._og_search_title(webpage)
|
or self._og_search_title(webpage)
|
||||||
)
|
)
|
||||||
|
|
||||||
file_id = self._search_regex(
|
file_id = self._search_regex(
|
||||||
|
@ -60,8 +60,8 @@ class HRTiBaseIE(InfoExtractor):
|
|||||||
language=self._APP_LANGUAGE,
|
language=self._APP_LANGUAGE,
|
||||||
application_id=self._APP_PUBLICATION_ID)
|
application_id=self._APP_PUBLICATION_ID)
|
||||||
|
|
||||||
self._login_url = (modules['user']['resources']['login']['uri'] +
|
self._login_url = (modules['user']['resources']['login']['uri']
|
||||||
'/format/json').format(session_id=self._session_id)
|
+ '/format/json').format(session_id=self._session_id)
|
||||||
|
|
||||||
self._logout_url = modules['user']['resources']['logout']['uri']
|
self._logout_url = modules['user']['resources']['logout']['uri']
|
||||||
|
|
||||||
|
@ -122,9 +122,9 @@ class InfoQIE(BokeCCBaseIE):
|
|||||||
formats = self._extract_bokecc_formats(webpage, video_id)
|
formats = self._extract_bokecc_formats(webpage, video_id)
|
||||||
else:
|
else:
|
||||||
formats = (
|
formats = (
|
||||||
self._extract_rtmp_video(webpage) +
|
self._extract_rtmp_video(webpage)
|
||||||
self._extract_http_video(webpage) +
|
+ self._extract_http_video(webpage)
|
||||||
self._extract_http_audio(webpage, video_id))
|
+ self._extract_http_audio(webpage, video_id))
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
@ -383,9 +383,9 @@ class IqiyiIE(InfoExtractor):
|
|||||||
self._sleep(5, video_id)
|
self._sleep(5, video_id)
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
title = (get_element_by_id('widget-videotitle', webpage) or
|
title = (get_element_by_id('widget-videotitle', webpage)
|
||||||
clean_html(get_element_by_attribute('class', 'mod-play-tit', webpage)) or
|
or clean_html(get_element_by_attribute('class', 'mod-play-tit', webpage))
|
||||||
self._html_search_regex(r'<span[^>]+data-videochanged-title="word"[^>]*>([^<]+)</span>', webpage, 'title'))
|
or self._html_search_regex(r'<span[^>]+data-videochanged-title="word"[^>]*>([^<]+)</span>', webpage, 'title'))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
@ -77,10 +77,10 @@ class ITVIE(InfoExtractor):
|
|||||||
return etree.SubElement(element, _add_ns(name))
|
return etree.SubElement(element, _add_ns(name))
|
||||||
|
|
||||||
production_id = (
|
production_id = (
|
||||||
params.get('data-video-autoplay-id') or
|
params.get('data-video-autoplay-id')
|
||||||
'%s#001' % (
|
or '%s#001' % (
|
||||||
params.get('data-video-episode-id') or
|
params.get('data-video-episode-id')
|
||||||
video_id.replace('a', '/')))
|
or video_id.replace('a', '/')))
|
||||||
|
|
||||||
req_env = etree.Element(_add_ns('soapenv:Envelope'))
|
req_env = etree.Element(_add_ns('soapenv:Envelope'))
|
||||||
_add_sub_element(req_env, 'soapenv:Header')
|
_add_sub_element(req_env, 'soapenv:Header')
|
||||||
|
@ -103,6 +103,11 @@ class KalturaIE(InfoExtractor):
|
|||||||
{
|
{
|
||||||
'url': 'https://www.kaltura.com:443/index.php/extwidget/preview/partner_id/1770401/uiconf_id/37307382/entry_id/0_58u8kme7/embed/iframe?&flashvars[streamerType]=auto',
|
'url': 'https://www.kaltura.com:443/index.php/extwidget/preview/partner_id/1770401/uiconf_id/37307382/entry_id/0_58u8kme7/embed/iframe?&flashvars[streamerType]=auto',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# unavailable source format
|
||||||
|
'url': 'kaltura:513551:1_66x4rg7o',
|
||||||
|
'only_matching': True,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -118,8 +123,8 @@ class KalturaIE(InfoExtractor):
|
|||||||
(?P<q2>['"])_?(?P<partner_id>(?:(?!(?P=q2)).)+)(?P=q2),.*?
|
(?P<q2>['"])_?(?P<partner_id>(?:(?!(?P=q2)).)+)(?P=q2),.*?
|
||||||
(?P<q3>['"])entry_?[Ii]d(?P=q3)\s*:\s*
|
(?P<q3>['"])entry_?[Ii]d(?P=q3)\s*:\s*
|
||||||
(?P<q4>['"])(?P<id>(?:(?!(?P=q4)).)+)(?P=q4)(?:,|\s*\})
|
(?P<q4>['"])(?P<id>(?:(?!(?P=q4)).)+)(?P=q4)(?:,|\s*\})
|
||||||
""", webpage) or
|
""", webpage)
|
||||||
re.search(
|
or re.search(
|
||||||
r'''(?xs)
|
r'''(?xs)
|
||||||
(?P<q1>["'])
|
(?P<q1>["'])
|
||||||
(?:https?:)?//cdnapi(?:sec)?\.kaltura\.com(?::\d+)?/(?:(?!(?P=q1)).)*\b(?:p|partner_id)/(?P<partner_id>\d+)(?:(?!(?P=q1)).)*
|
(?:https?:)?//cdnapi(?:sec)?\.kaltura\.com(?::\d+)?/(?:(?!(?P=q1)).)*\b(?:p|partner_id)/(?P<partner_id>\d+)(?:(?!(?P=q1)).)*
|
||||||
@ -132,8 +137,8 @@ class KalturaIE(InfoExtractor):
|
|||||||
\[\s*(?P<q2_1>["'])entry_?[Ii]d(?P=q2_1)\s*\]\s*=\s*
|
\[\s*(?P<q2_1>["'])entry_?[Ii]d(?P=q2_1)\s*\]\s*=\s*
|
||||||
)
|
)
|
||||||
(?P<q3>["'])(?P<id>(?:(?!(?P=q3)).)+)(?P=q3)
|
(?P<q3>["'])(?P<id>(?:(?!(?P=q3)).)+)(?P=q3)
|
||||||
''', webpage) or
|
''', webpage)
|
||||||
re.search(
|
or re.search(
|
||||||
r'''(?xs)
|
r'''(?xs)
|
||||||
<(?:iframe[^>]+src|meta[^>]+\bcontent)=(?P<q1>["'])
|
<(?:iframe[^>]+src|meta[^>]+\bcontent)=(?P<q1>["'])
|
||||||
(?:https?:)?//(?:(?:www|cdnapi(?:sec)?)\.)?kaltura\.com/(?:(?!(?P=q1)).)*\b(?:p|partner_id)/(?P<partner_id>\d+)
|
(?:https?:)?//(?:(?:www|cdnapi(?:sec)?)\.)?kaltura\.com/(?:(?!(?P=q1)).)*\b(?:p|partner_id)/(?P<partner_id>\d+)
|
||||||
@ -306,12 +311,17 @@ class KalturaIE(InfoExtractor):
|
|||||||
f['fileExt'] = 'mp4'
|
f['fileExt'] = 'mp4'
|
||||||
video_url = sign_url(
|
video_url = sign_url(
|
||||||
'%s/flavorId/%s' % (data_url, f['id']))
|
'%s/flavorId/%s' % (data_url, f['id']))
|
||||||
|
format_id = '%(fileExt)s-%(bitrate)s' % f
|
||||||
|
# Source format may not be available (e.g. kaltura:513551:1_66x4rg7o)
|
||||||
|
if f.get('isOriginal') is True and not self._is_valid_url(
|
||||||
|
video_url, entry_id, format_id):
|
||||||
|
continue
|
||||||
# audio-only has no videoCodecId (e.g. kaltura:1926081:0_c03e1b5g
|
# audio-only has no videoCodecId (e.g. kaltura:1926081:0_c03e1b5g
|
||||||
# -f mp4-56)
|
# -f mp4-56)
|
||||||
vcodec = 'none' if 'videoCodecId' not in f and f.get(
|
vcodec = 'none' if 'videoCodecId' not in f and f.get(
|
||||||
'frameRate') == 0 else f.get('videoCodecId')
|
'frameRate') == 0 else f.get('videoCodecId')
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': '%(fileExt)s-%(bitrate)s' % f,
|
'format_id': format_id,
|
||||||
'ext': f.get('fileExt'),
|
'ext': f.get('fileExt'),
|
||||||
'tbr': int_or_none(f['bitrate']),
|
'tbr': int_or_none(f['bitrate']),
|
||||||
'fps': int_or_none(f.get('frameRate')),
|
'fps': int_or_none(f.get('frameRate')),
|
||||||
|
@ -47,8 +47,8 @@ class KarriereVideosIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
title = (self._html_search_meta('title', webpage, default=None) or
|
title = (self._html_search_meta('title', webpage, default=None)
|
||||||
self._search_regex(r'<h1 class="title">([^<]+)</h1>', webpage, 'video title'))
|
or self._search_regex(r'<h1 class="title">([^<]+)</h1>', webpage, 'video title'))
|
||||||
|
|
||||||
video_id = self._search_regex(
|
video_id = self._search_regex(
|
||||||
r'/config/video/(.+?)\.xml', webpage, 'video id')
|
r'/config/video/(.+?)\.xml', webpage, 'video id')
|
||||||
|
@ -6,8 +6,8 @@ import re
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_str
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
clean_html,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
extract_attributes,
|
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
@ -19,6 +19,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class LecturioBaseIE(InfoExtractor):
|
class LecturioBaseIE(InfoExtractor):
|
||||||
|
_API_BASE_URL = 'https://app.lecturio.com/api/en/latest/html5/'
|
||||||
_LOGIN_URL = 'https://app.lecturio.com/en/login'
|
_LOGIN_URL = 'https://app.lecturio.com/en/login'
|
||||||
_NETRC_MACHINE = 'lecturio'
|
_NETRC_MACHINE = 'lecturio'
|
||||||
|
|
||||||
@ -67,51 +68,56 @@ class LecturioIE(LecturioBaseIE):
|
|||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
https://
|
https://
|
||||||
(?:
|
(?:
|
||||||
app\.lecturio\.com/[^/]+/(?P<id>[^/?#&]+)\.lecture|
|
app\.lecturio\.com/([^/]+/(?P<nt>[^/?#&]+)\.lecture|(?:\#/)?lecture/c/\d+/(?P<id>\d+))|
|
||||||
(?:www\.)?lecturio\.de/[^/]+/(?P<id_de>[^/?#&]+)\.vortrag
|
(?:www\.)?lecturio\.de/[^/]+/(?P<nt_de>[^/?#&]+)\.vortrag
|
||||||
)
|
)
|
||||||
'''
|
'''
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://app.lecturio.com/medical-courses/important-concepts-and-terms-introduction-to-microbiology.lecture#tab/videos',
|
'url': 'https://app.lecturio.com/medical-courses/important-concepts-and-terms-introduction-to-microbiology.lecture#tab/videos',
|
||||||
'md5': 'f576a797a5b7a5e4e4bbdfc25a6a6870',
|
'md5': '9a42cf1d8282a6311bf7211bbde26fde',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '39634',
|
'id': '39634',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Important Concepts and Terms – Introduction to Microbiology',
|
'title': 'Important Concepts and Terms — Introduction to Microbiology',
|
||||||
},
|
},
|
||||||
'skip': 'Requires lecturio account credentials',
|
'skip': 'Requires lecturio account credentials',
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://www.lecturio.de/jura/oeffentliches-recht-staatsexamen.vortrag',
|
'url': 'https://www.lecturio.de/jura/oeffentliches-recht-staatsexamen.vortrag',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://app.lecturio.com/#/lecture/c/6434/39634',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
_CC_LANGS = {
|
_CC_LANGS = {
|
||||||
|
'Arabic': 'ar',
|
||||||
|
'Bulgarian': 'bg',
|
||||||
'German': 'de',
|
'German': 'de',
|
||||||
'English': 'en',
|
'English': 'en',
|
||||||
'Spanish': 'es',
|
'Spanish': 'es',
|
||||||
|
'Persian': 'fa',
|
||||||
'French': 'fr',
|
'French': 'fr',
|
||||||
|
'Japanese': 'ja',
|
||||||
'Polish': 'pl',
|
'Polish': 'pl',
|
||||||
|
'Pashto': 'ps',
|
||||||
'Russian': 'ru',
|
'Russian': 'ru',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
display_id = mobj.group('id') or mobj.group('id_de')
|
nt = mobj.group('nt') or mobj.group('nt_de')
|
||||||
|
lecture_id = mobj.group('id')
|
||||||
webpage = self._download_webpage(
|
display_id = nt or lecture_id
|
||||||
'https://app.lecturio.com/en/lecture/%s/player.html' % display_id,
|
api_path = 'lectures/' + lecture_id if lecture_id else 'lecture/' + nt + '.json'
|
||||||
display_id)
|
video = self._download_json(
|
||||||
|
self._API_BASE_URL + api_path, display_id)
|
||||||
lecture_id = self._search_regex(
|
|
||||||
r'lecture_id\s*=\s*(?:L_)?(\d+)', webpage, 'lecture id')
|
|
||||||
|
|
||||||
api_url = self._search_regex(
|
|
||||||
r'lectureDataLink\s*:\s*(["\'])(?P<url>(?:(?!\1).)+)\1', webpage,
|
|
||||||
'api url', group='url')
|
|
||||||
|
|
||||||
video = self._download_json(api_url, display_id)
|
|
||||||
|
|
||||||
title = video['title'].strip()
|
title = video['title'].strip()
|
||||||
|
if not lecture_id:
|
||||||
|
pid = video.get('productId') or video.get('uid')
|
||||||
|
if pid:
|
||||||
|
spid = pid.split('_')
|
||||||
|
if spid and len(spid) == 2:
|
||||||
|
lecture_id = spid[1]
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for format_ in video['content']['media']:
|
for format_ in video['content']['media']:
|
||||||
@ -129,24 +135,30 @@ class LecturioIE(LecturioBaseIE):
|
|||||||
continue
|
continue
|
||||||
label = str_or_none(format_.get('label'))
|
label = str_or_none(format_.get('label'))
|
||||||
filesize = int_or_none(format_.get('fileSize'))
|
filesize = int_or_none(format_.get('fileSize'))
|
||||||
formats.append({
|
f = {
|
||||||
'url': file_url,
|
'url': file_url,
|
||||||
'format_id': label,
|
'format_id': label,
|
||||||
'filesize': float_or_none(filesize, invscale=1000)
|
'filesize': float_or_none(filesize, invscale=1000)
|
||||||
})
|
}
|
||||||
|
if label:
|
||||||
|
mobj = re.match(r'(\d+)p\s*\(([^)]+)\)', label)
|
||||||
|
if mobj:
|
||||||
|
f.update({
|
||||||
|
'format_id': mobj.group(2),
|
||||||
|
'height': int(mobj.group(1)),
|
||||||
|
})
|
||||||
|
formats.append(f)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
automatic_captions = {}
|
automatic_captions = {}
|
||||||
cc = self._parse_json(
|
captions = video.get('captions') or []
|
||||||
self._search_regex(
|
for cc in captions:
|
||||||
r'subtitleUrls\s*:\s*({.+?})\s*,', webpage, 'subtitles',
|
cc_url = cc.get('url')
|
||||||
default='{}'), display_id, fatal=False)
|
|
||||||
for cc_label, cc_url in cc.items():
|
|
||||||
cc_url = url_or_none(cc_url)
|
|
||||||
if not cc_url:
|
if not cc_url:
|
||||||
continue
|
continue
|
||||||
lang = self._search_regex(
|
cc_label = cc.get('translatedCode')
|
||||||
|
lang = cc.get('languageCode') or self._search_regex(
|
||||||
r'/([a-z]{2})_', cc_url, 'lang',
|
r'/([a-z]{2})_', cc_url, 'lang',
|
||||||
default=cc_label.split()[0] if cc_label else 'en')
|
default=cc_label.split()[0] if cc_label else 'en')
|
||||||
original_lang = self._search_regex(
|
original_lang = self._search_regex(
|
||||||
@ -160,7 +172,7 @@ class LecturioIE(LecturioBaseIE):
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': lecture_id,
|
'id': lecture_id or nt,
|
||||||
'title': title,
|
'title': title,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
@ -169,37 +181,40 @@ class LecturioIE(LecturioBaseIE):
|
|||||||
|
|
||||||
|
|
||||||
class LecturioCourseIE(LecturioBaseIE):
|
class LecturioCourseIE(LecturioBaseIE):
|
||||||
_VALID_URL = r'https://app\.lecturio\.com/[^/]+/(?P<id>[^/?#&]+)\.course'
|
_VALID_URL = r'https://app\.lecturio\.com/(?:[^/]+/(?P<nt>[^/?#&]+)\.course|(?:#/)?course/c/(?P<id>\d+))'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'https://app.lecturio.com/medical-courses/microbiology-introduction.course#/',
|
'url': 'https://app.lecturio.com/medical-courses/microbiology-introduction.course#/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'microbiology-introduction',
|
'id': 'microbiology-introduction',
|
||||||
'title': 'Microbiology: Introduction',
|
'title': 'Microbiology: Introduction',
|
||||||
|
'description': 'md5:13da8500c25880c6016ae1e6d78c386a',
|
||||||
},
|
},
|
||||||
'playlist_count': 45,
|
'playlist_count': 45,
|
||||||
'skip': 'Requires lecturio account credentials',
|
'skip': 'Requires lecturio account credentials',
|
||||||
}
|
}, {
|
||||||
|
'url': 'https://app.lecturio.com/#/course/c/6434',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
display_id = self._match_id(url)
|
nt, course_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
display_id = nt or course_id
|
||||||
webpage = self._download_webpage(url, display_id)
|
api_path = 'courses/' + course_id if course_id else 'course/content/' + nt + '.json'
|
||||||
|
course = self._download_json(
|
||||||
|
self._API_BASE_URL + api_path, display_id)
|
||||||
entries = []
|
entries = []
|
||||||
for mobj in re.finditer(
|
for lecture in course.get('lectures', []):
|
||||||
r'(?s)<[^>]+\bdata-url=(["\'])(?:(?!\1).)+\.lecture\b[^>]+>',
|
lecture_id = str_or_none(lecture.get('id'))
|
||||||
webpage):
|
lecture_url = lecture.get('url')
|
||||||
params = extract_attributes(mobj.group(0))
|
if lecture_url:
|
||||||
lecture_url = urljoin(url, params.get('data-url'))
|
lecture_url = urljoin(url, lecture_url)
|
||||||
lecture_id = params.get('data-id')
|
else:
|
||||||
|
lecture_url = 'https://app.lecturio.com/#/lecture/c/%s/%s' % (course_id, lecture_id)
|
||||||
entries.append(self.url_result(
|
entries.append(self.url_result(
|
||||||
lecture_url, ie=LecturioIE.ie_key(), video_id=lecture_id))
|
lecture_url, ie=LecturioIE.ie_key(), video_id=lecture_id))
|
||||||
|
return self.playlist_result(
|
||||||
title = self._search_regex(
|
entries, display_id, course.get('title'),
|
||||||
r'<span[^>]+class=["\']content-title[^>]+>([^<]+)', webpage,
|
clean_html(course.get('description')))
|
||||||
'title', default=None)
|
|
||||||
|
|
||||||
return self.playlist_result(entries, display_id, title)
|
|
||||||
|
|
||||||
|
|
||||||
class LecturioDeCourseIE(LecturioBaseIE):
|
class LecturioDeCourseIE(LecturioBaseIE):
|
||||||
|
@ -326,7 +326,7 @@ class LetvCloudIE(InfoExtractor):
|
|||||||
elif play_json.get('code'):
|
elif play_json.get('code'):
|
||||||
raise ExtractorError('Letv cloud returned error %d' % play_json['code'], expected=True)
|
raise ExtractorError('Letv cloud returned error %d' % play_json['code'], expected=True)
|
||||||
else:
|
else:
|
||||||
raise ExtractorError('Letv cloud returned an unknwon error')
|
raise ExtractorError('Letv cloud returned an unknown error')
|
||||||
|
|
||||||
def b64decode(s):
|
def b64decode(s):
|
||||||
return compat_b64decode(s).decode('utf-8')
|
return compat_b64decode(s).decode('utf-8')
|
||||||
|
42
youtube_dl/extractor/livejournal.py
Normal file
42
youtube_dl/extractor/livejournal.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class LiveJournalIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:[^.]+\.)?livejournal\.com/video/album/\d+.+?\bid=(?P<id>\d+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://andrei-bt.livejournal.com/video/album/407/?mode=view&id=51272',
|
||||||
|
'md5': 'adaf018388572ced8a6f301ace49d4b2',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1263729',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Истребители против БПЛА',
|
||||||
|
'upload_date': '20190624',
|
||||||
|
'timestamp': 1561406715,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
record = self._parse_json(self._search_regex(
|
||||||
|
r'Site\.page\s*=\s*({.+?});', webpage,
|
||||||
|
'page data'), video_id)['video']['record']
|
||||||
|
storage_id = compat_str(record['storageid'])
|
||||||
|
title = record.get('name')
|
||||||
|
if title:
|
||||||
|
# remove filename extension(.mp4, .mov, etc...)
|
||||||
|
title = title.rsplit('.', 1)[0]
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': record.get('thumbnail'),
|
||||||
|
'timestamp': int_or_none(record.get('timecreate')),
|
||||||
|
'url': 'eagleplatform:vc.videos.livejournal.com:' + storage_id,
|
||||||
|
'ie_key': 'EaglePlatform',
|
||||||
|
}
|
@ -82,6 +82,10 @@ class LiveLeakIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://www.liveleak.com/view?t=HvHi_1523016227',
|
'url': 'https://www.liveleak.com/view?t=HvHi_1523016227',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# No original video
|
||||||
|
'url': 'https://www.liveleak.com/view?t=C26ZZ_1558612804',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -134,11 +138,13 @@ class LiveLeakIE(InfoExtractor):
|
|||||||
orig_url = re.sub(r'\.mp4\.[^.]+', '', a_format['url'])
|
orig_url = re.sub(r'\.mp4\.[^.]+', '', a_format['url'])
|
||||||
if a_format['url'] != orig_url:
|
if a_format['url'] != orig_url:
|
||||||
format_id = a_format.get('format_id')
|
format_id = a_format.get('format_id')
|
||||||
formats.append({
|
format_id = 'original' + ('-' + format_id if format_id else '')
|
||||||
'format_id': 'original' + ('-' + format_id if format_id else ''),
|
if self._is_valid_url(orig_url, video_id, format_id):
|
||||||
'url': orig_url,
|
formats.append({
|
||||||
'preference': 1,
|
'format_id': format_id,
|
||||||
})
|
'url': orig_url,
|
||||||
|
'preference': 1,
|
||||||
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
info_dict['formats'] = formats
|
info_dict['formats'] = formats
|
||||||
|
|
||||||
|
@ -117,6 +117,10 @@ class LyndaIE(LyndaBaseIE):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://www.lynda.com/de/Graphic-Design-tutorials/Willkommen-Grundlagen-guten-Gestaltung/393570/393572-4.html',
|
'url': 'https://www.lynda.com/de/Graphic-Design-tutorials/Willkommen-Grundlagen-guten-Gestaltung/393570/393572-4.html',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# Status="NotFound", Message="Transcript not found"
|
||||||
|
'url': 'https://www.lynda.com/ASP-NET-tutorials/What-you-should-know/5034180/2811512-4.html',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _raise_unavailable(self, video_id):
|
def _raise_unavailable(self, video_id):
|
||||||
@ -247,12 +251,17 @@ class LyndaIE(LyndaBaseIE):
|
|||||||
|
|
||||||
def _get_subtitles(self, video_id):
|
def _get_subtitles(self, video_id):
|
||||||
url = 'https://www.lynda.com/ajax/player?videoId=%s&type=transcript' % video_id
|
url = 'https://www.lynda.com/ajax/player?videoId=%s&type=transcript' % video_id
|
||||||
subs = self._download_json(url, None, False)
|
subs = self._download_webpage(
|
||||||
|
url, video_id, 'Downloading subtitles JSON', fatal=False)
|
||||||
|
if not subs or 'Status="NotFound"' in subs:
|
||||||
|
return {}
|
||||||
|
subs = self._parse_json(subs, video_id, fatal=False)
|
||||||
|
if not subs:
|
||||||
|
return {}
|
||||||
fixed_subs = self._fix_subtitles(subs)
|
fixed_subs = self._fix_subtitles(subs)
|
||||||
if fixed_subs:
|
if fixed_subs:
|
||||||
return {'en': [{'ext': 'srt', 'data': fixed_subs}]}
|
return {'en': [{'ext': 'srt', 'data': fixed_subs}]}
|
||||||
else:
|
return {}
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
class LyndaCourseIE(LyndaBaseIE):
|
class LyndaCourseIE(LyndaBaseIE):
|
||||||
|
@ -79,6 +79,10 @@ class MGTVIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'tbr': tbr,
|
'tbr': tbr,
|
||||||
'protocol': 'm3u8_native',
|
'protocol': 'm3u8_native',
|
||||||
|
'http_headers': {
|
||||||
|
'Referer': url,
|
||||||
|
},
|
||||||
|
'format_note': stream.get('name'),
|
||||||
})
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
@ -80,8 +80,8 @@ class MotherlessIE(InfoExtractor):
|
|||||||
video_url = (self._html_search_regex(
|
video_url = (self._html_search_regex(
|
||||||
(r'setup\(\{\s*["\']file["\']\s*:\s*(["\'])(?P<url>(?:(?!\1).)+)\1',
|
(r'setup\(\{\s*["\']file["\']\s*:\s*(["\'])(?P<url>(?:(?!\1).)+)\1',
|
||||||
r'fileurl\s*=\s*(["\'])(?P<url>(?:(?!\1).)+)\1'),
|
r'fileurl\s*=\s*(["\'])(?P<url>(?:(?!\1).)+)\1'),
|
||||||
webpage, 'video URL', default=None, group='url') or
|
webpage, 'video URL', default=None, group='url')
|
||||||
'http://cdn4.videos.motherlessmedia.com/videos/%s.mp4?fs=opencloud' % video_id)
|
or 'http://cdn4.videos.motherlessmedia.com/videos/%s.mp4?fs=opencloud' % video_id)
|
||||||
age_limit = self._rta_search(webpage)
|
age_limit = self._rta_search(webpage)
|
||||||
view_count = str_to_int(self._html_search_regex(
|
view_count = str_to_int(self._html_search_regex(
|
||||||
r'<strong>Views</strong>\s+([^<]+)<',
|
r'<strong>Views</strong>\s+([^<]+)<',
|
||||||
|
@ -84,8 +84,8 @@ class NDTVIE(InfoExtractor):
|
|||||||
|
|
||||||
# '__title' does not contain extra words such as sub-site name, "Video" etc.
|
# '__title' does not contain extra words such as sub-site name, "Video" etc.
|
||||||
title = compat_urllib_parse_unquote_plus(
|
title = compat_urllib_parse_unquote_plus(
|
||||||
self._search_regex(r"__title\s*=\s*'([^']+)'", webpage, 'title', default=None) or
|
self._search_regex(r"__title\s*=\s*'([^']+)'", webpage, 'title', default=None)
|
||||||
self._og_search_title(webpage))
|
or self._og_search_title(webpage))
|
||||||
|
|
||||||
filename = self._search_regex(
|
filename = self._search_regex(
|
||||||
r"(?:__)?filename\s*[:=]\s*'([^']+)'", webpage, 'video filename')
|
r"(?:__)?filename\s*[:=]\s*'([^']+)'", webpage, 'video filename')
|
||||||
|
@ -180,8 +180,8 @@ class AppleDailyIE(NextMediaIE):
|
|||||||
_URL_PATTERN = r'\{url: \'(.+)\'\}'
|
_URL_PATTERN = r'\{url: \'(.+)\'\}'
|
||||||
|
|
||||||
def _fetch_title(self, page):
|
def _fetch_title(self, page):
|
||||||
return (self._html_search_regex(r'<h1 id="h1">([^<>]+)</h1>', page, 'news title', default=None) or
|
return (self._html_search_regex(r'<h1 id="h1">([^<>]+)</h1>', page, 'news title', default=None)
|
||||||
self._html_search_meta('description', page, 'news title'))
|
or self._html_search_meta('description', page, 'news title'))
|
||||||
|
|
||||||
def _fetch_thumbnail(self, page):
|
def _fetch_thumbnail(self, page):
|
||||||
return self._html_search_regex(r"setInitialImage\(\'([^']+)'\)", page, 'video thumbnail', fatal=False)
|
return self._html_search_regex(r"setInitialImage\(\'([^']+)'\)", page, 'video thumbnail', fatal=False)
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..utils import (
|
|
||||||
clean_html,
|
|
||||||
determine_ext,
|
|
||||||
int_or_none,
|
|
||||||
qualities,
|
|
||||||
urlencode_postdata,
|
|
||||||
xpath_text,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class NFBIE(InfoExtractor):
|
|
||||||
IE_NAME = 'nfb'
|
|
||||||
IE_DESC = 'National Film Board of Canada'
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:nfb|onf)\.ca/film/(?P<id>[\da-z_-]+)'
|
|
||||||
|
|
||||||
_TEST = {
|
|
||||||
'url': 'https://www.nfb.ca/film/qallunaat_why_white_people_are_funny',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'qallunaat_why_white_people_are_funny',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'Qallunaat! Why White People Are Funny ',
|
|
||||||
'description': 'md5:6b8e32dde3abf91e58857b174916620c',
|
|
||||||
'duration': 3128,
|
|
||||||
'creator': 'Mark Sandiford',
|
|
||||||
'uploader': 'Mark Sandiford',
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
# rtmp download
|
|
||||||
'skip_download': True,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
|
|
||||||
config = self._download_xml(
|
|
||||||
'https://www.nfb.ca/film/%s/player_config' % video_id,
|
|
||||||
video_id, 'Downloading player config XML',
|
|
||||||
data=urlencode_postdata({'getConfig': 'true'}),
|
|
||||||
headers={
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'X-NFB-Referer': 'http://www.nfb.ca/medias/flash/NFBVideoPlayer.swf'
|
|
||||||
})
|
|
||||||
|
|
||||||
title, description, thumbnail, duration, uploader, author = [None] * 6
|
|
||||||
thumbnails, formats = [[]] * 2
|
|
||||||
subtitles = {}
|
|
||||||
|
|
||||||
for media in config.findall('./player/stream/media'):
|
|
||||||
if media.get('type') == 'posterImage':
|
|
||||||
quality_key = qualities(('low', 'high'))
|
|
||||||
thumbnails = []
|
|
||||||
for asset in media.findall('assets/asset'):
|
|
||||||
asset_url = xpath_text(asset, 'default/url', default=None)
|
|
||||||
if not asset_url:
|
|
||||||
continue
|
|
||||||
quality = asset.get('quality')
|
|
||||||
thumbnails.append({
|
|
||||||
'url': asset_url,
|
|
||||||
'id': quality,
|
|
||||||
'preference': quality_key(quality),
|
|
||||||
})
|
|
||||||
elif media.get('type') == 'video':
|
|
||||||
title = xpath_text(media, 'title', fatal=True)
|
|
||||||
for asset in media.findall('assets/asset'):
|
|
||||||
quality = asset.get('quality')
|
|
||||||
height = int_or_none(self._search_regex(
|
|
||||||
r'^(\d+)[pP]$', quality or '', 'height', default=None))
|
|
||||||
for node in asset:
|
|
||||||
streamer = xpath_text(node, 'streamerURI', default=None)
|
|
||||||
if not streamer:
|
|
||||||
continue
|
|
||||||
play_path = xpath_text(node, 'url', default=None)
|
|
||||||
if not play_path:
|
|
||||||
continue
|
|
||||||
formats.append({
|
|
||||||
'url': streamer,
|
|
||||||
'app': streamer.split('/', 3)[3],
|
|
||||||
'play_path': play_path,
|
|
||||||
'rtmp_live': False,
|
|
||||||
'ext': 'flv',
|
|
||||||
'format_id': '%s-%s' % (node.tag, quality) if quality else node.tag,
|
|
||||||
'height': height,
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
description = clean_html(xpath_text(media, 'description'))
|
|
||||||
uploader = xpath_text(media, 'author')
|
|
||||||
duration = int_or_none(media.get('duration'))
|
|
||||||
for subtitle in media.findall('./subtitles/subtitle'):
|
|
||||||
subtitle_url = xpath_text(subtitle, 'url', default=None)
|
|
||||||
if not subtitle_url:
|
|
||||||
continue
|
|
||||||
lang = xpath_text(subtitle, 'lang', default='en')
|
|
||||||
subtitles.setdefault(lang, []).append({
|
|
||||||
'url': subtitle_url,
|
|
||||||
'ext': (subtitle.get('format') or determine_ext(subtitle_url)).lower(),
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'thumbnails': thumbnails,
|
|
||||||
'duration': duration,
|
|
||||||
'creator': uploader,
|
|
||||||
'uploader': uploader,
|
|
||||||
'formats': formats,
|
|
||||||
'subtitles': subtitles,
|
|
||||||
}
|
|
@ -369,14 +369,14 @@ class NiconicoIE(InfoExtractor):
|
|||||||
video_detail = watch_api_data.get('videoDetail', {})
|
video_detail = watch_api_data.get('videoDetail', {})
|
||||||
|
|
||||||
thumbnail = (
|
thumbnail = (
|
||||||
get_video_info(['thumbnail_url', 'thumbnailURL']) or
|
get_video_info(['thumbnail_url', 'thumbnailURL'])
|
||||||
self._html_search_meta('image', webpage, 'thumbnail', default=None) or
|
or self._html_search_meta('image', webpage, 'thumbnail', default=None)
|
||||||
video_detail.get('thumbnail'))
|
or video_detail.get('thumbnail'))
|
||||||
|
|
||||||
description = get_video_info('description')
|
description = get_video_info('description')
|
||||||
|
|
||||||
timestamp = (parse_iso8601(get_video_info('first_retrieve')) or
|
timestamp = (parse_iso8601(get_video_info('first_retrieve'))
|
||||||
unified_timestamp(get_video_info('postedDateTime')))
|
or unified_timestamp(get_video_info('postedDateTime')))
|
||||||
if not timestamp:
|
if not timestamp:
|
||||||
match = self._html_search_meta('datePublished', webpage, 'date published', default=None)
|
match = self._html_search_meta('datePublished', webpage, 'date published', default=None)
|
||||||
if match:
|
if match:
|
||||||
@ -395,9 +395,9 @@ class NiconicoIE(InfoExtractor):
|
|||||||
view_count = int_or_none(match.replace(',', ''))
|
view_count = int_or_none(match.replace(',', ''))
|
||||||
view_count = view_count or video_detail.get('viewCount')
|
view_count = view_count or video_detail.get('viewCount')
|
||||||
|
|
||||||
comment_count = (int_or_none(get_video_info('comment_num')) or
|
comment_count = (int_or_none(get_video_info('comment_num'))
|
||||||
video_detail.get('commentCount') or
|
or video_detail.get('commentCount')
|
||||||
try_get(api_data, lambda x: x['thread']['commentCount']))
|
or try_get(api_data, lambda x: x['thread']['commentCount']))
|
||||||
if not comment_count:
|
if not comment_count:
|
||||||
match = self._html_search_regex(
|
match = self._html_search_regex(
|
||||||
r'>Comments: <strong[^>]*>([^<]+)</strong>',
|
r'>Comments: <strong[^>]*>([^<]+)</strong>',
|
||||||
@ -406,11 +406,11 @@ class NiconicoIE(InfoExtractor):
|
|||||||
comment_count = int_or_none(match.replace(',', ''))
|
comment_count = int_or_none(match.replace(',', ''))
|
||||||
|
|
||||||
duration = (parse_duration(
|
duration = (parse_duration(
|
||||||
get_video_info('length') or
|
get_video_info('length')
|
||||||
self._html_search_meta(
|
or self._html_search_meta(
|
||||||
'video:duration', webpage, 'video duration', default=None)) or
|
'video:duration', webpage, 'video duration', default=None))
|
||||||
video_detail.get('length') or
|
or video_detail.get('length')
|
||||||
get_video_info('duration'))
|
or get_video_info('duration'))
|
||||||
|
|
||||||
webpage_url = get_video_info('watch_url') or url
|
webpage_url = get_video_info('watch_url') or url
|
||||||
|
|
||||||
|
@ -1,212 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..compat import compat_urlparse
|
|
||||||
from ..utils import (
|
|
||||||
ExtractorError,
|
|
||||||
NO_DEFAULT,
|
|
||||||
sanitized_Request,
|
|
||||||
urlencode_postdata,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class NovaMovIE(InfoExtractor):
|
|
||||||
IE_NAME = 'novamov'
|
|
||||||
IE_DESC = 'NovaMov'
|
|
||||||
|
|
||||||
_VALID_URL_TEMPLATE = r'''(?x)
|
|
||||||
http://
|
|
||||||
(?:
|
|
||||||
(?:www\.)?%(host)s/(?:file|video|mobile/\#/videos)/|
|
|
||||||
(?:(?:embed|www)\.)%(host)s/embed(?:\.php|/)?\?(?:.*?&)?\bv=
|
|
||||||
)
|
|
||||||
(?P<id>[a-z\d]{13})
|
|
||||||
'''
|
|
||||||
_VALID_URL = _VALID_URL_TEMPLATE % {'host': r'novamov\.com'}
|
|
||||||
|
|
||||||
_HOST = 'www.novamov.com'
|
|
||||||
|
|
||||||
_FILE_DELETED_REGEX = r'This file no longer exists on our servers!</h2>'
|
|
||||||
_FILEKEY_REGEX = r'flashvars\.filekey=(?P<filekey>"?[^"]+"?);'
|
|
||||||
_TITLE_REGEX = r'(?s)<div class="v_tab blockborder rounded5" id="v_tab1">\s*<h3>([^<]+)</h3>'
|
|
||||||
_DESCRIPTION_REGEX = r'(?s)<div class="v_tab blockborder rounded5" id="v_tab1">\s*<h3>[^<]+</h3><p>([^<]+)</p>'
|
|
||||||
_URL_TEMPLATE = 'http://%s/video/%s'
|
|
||||||
|
|
||||||
_TEST = None
|
|
||||||
|
|
||||||
def _check_existence(self, webpage, video_id):
|
|
||||||
if re.search(self._FILE_DELETED_REGEX, webpage) is not None:
|
|
||||||
raise ExtractorError('Video %s does not exist' % video_id, expected=True)
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
|
|
||||||
url = self._URL_TEMPLATE % (self._HOST, video_id)
|
|
||||||
|
|
||||||
webpage = self._download_webpage(
|
|
||||||
url, video_id, 'Downloading video page')
|
|
||||||
|
|
||||||
self._check_existence(webpage, video_id)
|
|
||||||
|
|
||||||
def extract_filekey(default=NO_DEFAULT):
|
|
||||||
filekey = self._search_regex(
|
|
||||||
self._FILEKEY_REGEX, webpage, 'filekey', default=default)
|
|
||||||
if filekey is not default and (filekey[0] != '"' or filekey[-1] != '"'):
|
|
||||||
return self._search_regex(
|
|
||||||
r'var\s+%s\s*=\s*"([^"]+)"' % re.escape(filekey), webpage, 'filekey', default=default)
|
|
||||||
else:
|
|
||||||
return filekey
|
|
||||||
|
|
||||||
filekey = extract_filekey(default=None)
|
|
||||||
|
|
||||||
if not filekey:
|
|
||||||
fields = self._hidden_inputs(webpage)
|
|
||||||
post_url = self._search_regex(
|
|
||||||
r'<form[^>]+action=(["\'])(?P<url>.+?)\1', webpage,
|
|
||||||
'post url', default=url, group='url')
|
|
||||||
if not post_url.startswith('http'):
|
|
||||||
post_url = compat_urlparse.urljoin(url, post_url)
|
|
||||||
request = sanitized_Request(
|
|
||||||
post_url, urlencode_postdata(fields))
|
|
||||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
|
||||||
request.add_header('Referer', post_url)
|
|
||||||
webpage = self._download_webpage(
|
|
||||||
request, video_id, 'Downloading continue to the video page')
|
|
||||||
self._check_existence(webpage, video_id)
|
|
||||||
|
|
||||||
filekey = extract_filekey()
|
|
||||||
|
|
||||||
title = self._html_search_regex(self._TITLE_REGEX, webpage, 'title')
|
|
||||||
description = self._html_search_regex(self._DESCRIPTION_REGEX, webpage, 'description', default='', fatal=False)
|
|
||||||
|
|
||||||
api_response = self._download_webpage(
|
|
||||||
'http://%s/api/player.api.php?key=%s&file=%s' % (self._HOST, filekey, video_id), video_id,
|
|
||||||
'Downloading video api response')
|
|
||||||
|
|
||||||
response = compat_urlparse.parse_qs(api_response)
|
|
||||||
|
|
||||||
if 'error_msg' in response:
|
|
||||||
raise ExtractorError('%s returned error: %s' % (self.IE_NAME, response['error_msg'][0]), expected=True)
|
|
||||||
|
|
||||||
video_url = response['url'][0]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'url': video_url,
|
|
||||||
'title': title,
|
|
||||||
'description': description
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class WholeCloudIE(NovaMovIE):
|
|
||||||
IE_NAME = 'wholecloud'
|
|
||||||
IE_DESC = 'WholeCloud'
|
|
||||||
|
|
||||||
_VALID_URL = NovaMovIE._VALID_URL_TEMPLATE % {'host': r'(?:wholecloud\.net|movshare\.(?:net|sx|ag))'}
|
|
||||||
|
|
||||||
_HOST = 'www.wholecloud.net'
|
|
||||||
|
|
||||||
_FILE_DELETED_REGEX = r'>This file no longer exists on our servers.<'
|
|
||||||
_TITLE_REGEX = r'<strong>Title:</strong> ([^<]+)</p>'
|
|
||||||
_DESCRIPTION_REGEX = r'<strong>Description:</strong> ([^<]+)</p>'
|
|
||||||
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.wholecloud.net/video/559e28be54d96',
|
|
||||||
'md5': 'abd31a2132947262c50429e1d16c1bfd',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '559e28be54d96',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'dissapeared image',
|
|
||||||
'description': 'optical illusion dissapeared image magic illusion',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class NowVideoIE(NovaMovIE):
|
|
||||||
IE_NAME = 'nowvideo'
|
|
||||||
IE_DESC = 'NowVideo'
|
|
||||||
|
|
||||||
_VALID_URL = NovaMovIE._VALID_URL_TEMPLATE % {'host': r'nowvideo\.(?:to|ch|ec|sx|eu|at|ag|co|li)'}
|
|
||||||
|
|
||||||
_HOST = 'www.nowvideo.to'
|
|
||||||
|
|
||||||
_FILE_DELETED_REGEX = r'>This file no longer exists on our servers.<'
|
|
||||||
_TITLE_REGEX = r'<h4>([^<]+)</h4>'
|
|
||||||
_DESCRIPTION_REGEX = r'</h4>\s*<p>([^<]+)</p>'
|
|
||||||
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.nowvideo.sx/video/f1d6fce9a968b',
|
|
||||||
'md5': '12c82cad4f2084881d8bc60ee29df092',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'f1d6fce9a968b',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'youtubedl test video BaWjenozKc',
|
|
||||||
'description': 'Description',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class VideoWeedIE(NovaMovIE):
|
|
||||||
IE_NAME = 'videoweed'
|
|
||||||
IE_DESC = 'VideoWeed'
|
|
||||||
|
|
||||||
_VALID_URL = NovaMovIE._VALID_URL_TEMPLATE % {'host': r'videoweed\.(?:es|com)'}
|
|
||||||
|
|
||||||
_HOST = 'www.videoweed.es'
|
|
||||||
|
|
||||||
_FILE_DELETED_REGEX = r'>This file no longer exists on our servers.<'
|
|
||||||
_TITLE_REGEX = r'<h1 class="text_shadow">([^<]+)</h1>'
|
|
||||||
_URL_TEMPLATE = 'http://%s/file/%s'
|
|
||||||
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.videoweed.es/file/b42178afbea14',
|
|
||||||
'md5': 'abd31a2132947262c50429e1d16c1bfd',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'b42178afbea14',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'optical illusion dissapeared image magic illusion',
|
|
||||||
'description': ''
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CloudTimeIE(NovaMovIE):
|
|
||||||
IE_NAME = 'cloudtime'
|
|
||||||
IE_DESC = 'CloudTime'
|
|
||||||
|
|
||||||
_VALID_URL = NovaMovIE._VALID_URL_TEMPLATE % {'host': r'cloudtime\.to'}
|
|
||||||
|
|
||||||
_HOST = 'www.cloudtime.to'
|
|
||||||
|
|
||||||
_FILE_DELETED_REGEX = r'>This file no longer exists on our servers.<'
|
|
||||||
_TITLE_REGEX = r'<div[^>]+class=["\']video_det["\'][^>]*>\s*<strong>([^<]+)</strong>'
|
|
||||||
|
|
||||||
_TEST = None
|
|
||||||
|
|
||||||
|
|
||||||
class AuroraVidIE(NovaMovIE):
|
|
||||||
IE_NAME = 'auroravid'
|
|
||||||
IE_DESC = 'AuroraVid'
|
|
||||||
|
|
||||||
_VALID_URL = NovaMovIE._VALID_URL_TEMPLATE % {'host': r'auroravid\.to'}
|
|
||||||
|
|
||||||
_HOST = 'www.auroravid.to'
|
|
||||||
|
|
||||||
_FILE_DELETED_REGEX = r'This file no longer exists on our servers!<'
|
|
||||||
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://www.auroravid.to/video/4rurhn9x446jj',
|
|
||||||
'md5': '7205f346a52bbeba427603ba10d4b935',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '4rurhn9x446jj',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'search engine optimization',
|
|
||||||
'description': 'search engine optimization is used to rank the web page in the google search engine'
|
|
||||||
},
|
|
||||||
'skip': '"Invalid token" errors abound (in web interface as well as youtube-dl, there is nothing we can do about it.)'
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.auroravid.to/embed/?v=4rurhn9x446jj',
|
|
||||||
'only_matching': True,
|
|
||||||
}]
|
|
@ -45,8 +45,8 @@ class NRKBaseIE(InfoExtractor):
|
|||||||
entries = []
|
entries = []
|
||||||
|
|
||||||
conviva = data.get('convivaStatistics') or {}
|
conviva = data.get('convivaStatistics') or {}
|
||||||
live = (data.get('mediaElementType') == 'Live' or
|
live = (data.get('mediaElementType') == 'Live'
|
||||||
data.get('isLive') is True or conviva.get('isLive'))
|
or data.get('isLive') is True or conviva.get('isLive'))
|
||||||
|
|
||||||
def make_title(t):
|
def make_title(t):
|
||||||
return self._live_title(t) if live else t
|
return self._live_title(t) if live else t
|
||||||
|
@ -31,8 +31,8 @@ class OoyalaBaseIE(InfoExtractor):
|
|||||||
title = metadata['title']
|
title = metadata['title']
|
||||||
|
|
||||||
auth_data = self._download_json(
|
auth_data = self._download_json(
|
||||||
self._AUTHORIZATION_URL_TEMPLATE % (pcode, embed_code) +
|
self._AUTHORIZATION_URL_TEMPLATE % (pcode, embed_code)
|
||||||
compat_urllib_parse_urlencode({
|
+ compat_urllib_parse_urlencode({
|
||||||
'domain': domain,
|
'domain': domain,
|
||||||
'supportedFormats': supportedformats or 'mp4,rtmp,m3u8,hds,dash,smooth',
|
'supportedFormats': supportedformats or 'mp4,rtmp,m3u8,hds,dash,smooth',
|
||||||
'embedToken': embed_token,
|
'embedToken': embed_token,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -5,26 +5,27 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_str,
|
# compat_str,
|
||||||
compat_HTTPError,
|
compat_HTTPError,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
clean_html,
|
clean_html,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
remove_end,
|
# remove_end,
|
||||||
|
str_or_none,
|
||||||
strip_or_none,
|
strip_or_none,
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
urljoin,
|
# urljoin,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PacktPubBaseIE(InfoExtractor):
|
class PacktPubBaseIE(InfoExtractor):
|
||||||
_PACKT_BASE = 'https://www.packtpub.com'
|
# _PACKT_BASE = 'https://www.packtpub.com'
|
||||||
_MAPT_REST = '%s/mapt-rest' % _PACKT_BASE
|
_STATIC_PRODUCTS_BASE = 'https://static.packt-cdn.com/products/'
|
||||||
|
|
||||||
|
|
||||||
class PacktPubIE(PacktPubBaseIE):
|
class PacktPubIE(PacktPubBaseIE):
|
||||||
_VALID_URL = r'https?://(?:(?:www\.)?packtpub\.com/mapt|subscription\.packtpub\.com)/video/[^/]+/(?P<course_id>\d+)/(?P<chapter_id>\d+)/(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:(?:www\.)?packtpub\.com/mapt|subscription\.packtpub\.com)/video/[^/]+/(?P<course_id>\d+)/(?P<chapter_id>[^/]+)/(?P<id>[^/]+)(?:/(?P<display_id>[^/?&#]+))?'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://www.packtpub.com/mapt/video/web-development/9781787122215/20528/20530/Project+Intro',
|
'url': 'https://www.packtpub.com/mapt/video/web-development/9781787122215/20528/20530/Project+Intro',
|
||||||
@ -40,6 +41,9 @@ class PacktPubIE(PacktPubBaseIE):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://subscription.packtpub.com/video/web_development/9781787122215/20528/20530/project-intro',
|
'url': 'https://subscription.packtpub.com/video/web_development/9781787122215/20528/20530/project-intro',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://subscription.packtpub.com/video/programming/9781838988906/p1/video1_1/business-card-project',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
_NETRC_MACHINE = 'packtpub'
|
_NETRC_MACHINE = 'packtpub'
|
||||||
_TOKEN = None
|
_TOKEN = None
|
||||||
@ -50,9 +54,9 @@ class PacktPubIE(PacktPubBaseIE):
|
|||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self._TOKEN = self._download_json(
|
self._TOKEN = self._download_json(
|
||||||
self._MAPT_REST + '/users/tokens', None,
|
'https://services.packtpub.com/auth-v1/users/tokens', None,
|
||||||
'Downloading Authorization Token', data=json.dumps({
|
'Downloading Authorization Token', data=json.dumps({
|
||||||
'email': username,
|
'username': username,
|
||||||
'password': password,
|
'password': password,
|
||||||
}).encode())['data']['access']
|
}).encode())['data']['access']
|
||||||
except ExtractorError as e:
|
except ExtractorError as e:
|
||||||
@ -61,54 +65,40 @@ class PacktPubIE(PacktPubBaseIE):
|
|||||||
raise ExtractorError(message, expected=True)
|
raise ExtractorError(message, expected=True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def _handle_error(self, response):
|
|
||||||
if response.get('status') != 'success':
|
|
||||||
raise ExtractorError(
|
|
||||||
'% said: %s' % (self.IE_NAME, response['message']),
|
|
||||||
expected=True)
|
|
||||||
|
|
||||||
def _download_json(self, *args, **kwargs):
|
|
||||||
response = super(PacktPubIE, self)._download_json(*args, **kwargs)
|
|
||||||
self._handle_error(response)
|
|
||||||
return response
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
course_id, chapter_id, video_id, display_id = re.match(self._VALID_URL, url).groups()
|
||||||
course_id, chapter_id, video_id = mobj.group(
|
|
||||||
'course_id', 'chapter_id', 'id')
|
|
||||||
|
|
||||||
headers = {}
|
headers = {}
|
||||||
if self._TOKEN:
|
if self._TOKEN:
|
||||||
headers['Authorization'] = 'Bearer ' + self._TOKEN
|
headers['Authorization'] = 'Bearer ' + self._TOKEN
|
||||||
video = self._download_json(
|
try:
|
||||||
'%s/users/me/products/%s/chapters/%s/sections/%s'
|
video_url = self._download_json(
|
||||||
% (self._MAPT_REST, course_id, chapter_id, video_id), video_id,
|
'https://services.packtpub.com/products-v1/products/%s/%s/%s' % (course_id, chapter_id, video_id), video_id,
|
||||||
'Downloading JSON video', headers=headers)['data']
|
'Downloading JSON video', headers=headers)['data']
|
||||||
|
except ExtractorError as e:
|
||||||
|
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 400:
|
||||||
|
self.raise_login_required('This video is locked')
|
||||||
|
raise
|
||||||
|
|
||||||
content = video.get('content')
|
# TODO: find a better way to avoid duplicating course requests
|
||||||
if not content:
|
# metadata = self._download_json(
|
||||||
self.raise_login_required('This video is locked')
|
# '%s/products/%s/chapters/%s/sections/%s/metadata'
|
||||||
|
# % (self._MAPT_REST, course_id, chapter_id, video_id),
|
||||||
|
# video_id)['data']
|
||||||
|
|
||||||
video_url = content['file']
|
# title = metadata['pageTitle']
|
||||||
|
# course_title = metadata.get('title')
|
||||||
metadata = self._download_json(
|
# if course_title:
|
||||||
'%s/products/%s/chapters/%s/sections/%s/metadata'
|
# title = remove_end(title, ' - %s' % course_title)
|
||||||
% (self._MAPT_REST, course_id, chapter_id, video_id),
|
# timestamp = unified_timestamp(metadata.get('publicationDate'))
|
||||||
video_id)['data']
|
# thumbnail = urljoin(self._PACKT_BASE, metadata.get('filepath'))
|
||||||
|
|
||||||
title = metadata['pageTitle']
|
|
||||||
course_title = metadata.get('title')
|
|
||||||
if course_title:
|
|
||||||
title = remove_end(title, ' - %s' % course_title)
|
|
||||||
timestamp = unified_timestamp(metadata.get('publicationDate'))
|
|
||||||
thumbnail = urljoin(self._PACKT_BASE, metadata.get('filepath'))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'title': title,
|
'title': display_id or video_id, # title,
|
||||||
'thumbnail': thumbnail,
|
# 'thumbnail': thumbnail,
|
||||||
'timestamp': timestamp,
|
# 'timestamp': timestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -119,6 +109,7 @@ class PacktPubCourseIE(PacktPubBaseIE):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '9781787122215',
|
'id': '9781787122215',
|
||||||
'title': 'Learn Nodejs by building 12 projects [Video]',
|
'title': 'Learn Nodejs by building 12 projects [Video]',
|
||||||
|
'description': 'md5:489da8d953f416e51927b60a1c7db0aa',
|
||||||
},
|
},
|
||||||
'playlist_count': 90,
|
'playlist_count': 90,
|
||||||
}, {
|
}, {
|
||||||
@ -136,35 +127,38 @@ class PacktPubCourseIE(PacktPubBaseIE):
|
|||||||
url, course_id = mobj.group('url', 'id')
|
url, course_id = mobj.group('url', 'id')
|
||||||
|
|
||||||
course = self._download_json(
|
course = self._download_json(
|
||||||
'%s/products/%s/metadata' % (self._MAPT_REST, course_id),
|
self._STATIC_PRODUCTS_BASE + '%s/toc' % course_id, course_id)
|
||||||
course_id)['data']
|
metadata = self._download_json(
|
||||||
|
self._STATIC_PRODUCTS_BASE + '%s/summary' % course_id,
|
||||||
|
course_id, fatal=False) or {}
|
||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
for chapter_num, chapter in enumerate(course['tableOfContents'], 1):
|
for chapter_num, chapter in enumerate(course['chapters'], 1):
|
||||||
if chapter.get('type') != 'chapter':
|
chapter_id = str_or_none(chapter.get('id'))
|
||||||
continue
|
sections = chapter.get('sections')
|
||||||
children = chapter.get('children')
|
if not chapter_id or not isinstance(sections, list):
|
||||||
if not isinstance(children, list):
|
|
||||||
continue
|
continue
|
||||||
chapter_info = {
|
chapter_info = {
|
||||||
'chapter': chapter.get('title'),
|
'chapter': chapter.get('title'),
|
||||||
'chapter_number': chapter_num,
|
'chapter_number': chapter_num,
|
||||||
'chapter_id': chapter.get('id'),
|
'chapter_id': chapter_id,
|
||||||
}
|
}
|
||||||
for section in children:
|
for section in sections:
|
||||||
if section.get('type') != 'section':
|
section_id = str_or_none(section.get('id'))
|
||||||
continue
|
if not section_id or section.get('contentType') != 'video':
|
||||||
section_url = section.get('seoUrl')
|
|
||||||
if not isinstance(section_url, compat_str):
|
|
||||||
continue
|
continue
|
||||||
entry = {
|
entry = {
|
||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
'url': urljoin(url + '/', section_url),
|
'url': '/'.join([url, chapter_id, section_id]),
|
||||||
'title': strip_or_none(section.get('title')),
|
'title': strip_or_none(section.get('title')),
|
||||||
'description': clean_html(section.get('summary')),
|
'description': clean_html(section.get('summary')),
|
||||||
|
'thumbnail': metadata.get('coverImage'),
|
||||||
|
'timestamp': unified_timestamp(metadata.get('publicationDate')),
|
||||||
'ie_key': PacktPubIE.ie_key(),
|
'ie_key': PacktPubIE.ie_key(),
|
||||||
}
|
}
|
||||||
entry.update(chapter_info)
|
entry.update(chapter_info)
|
||||||
entries.append(entry)
|
entries.append(entry)
|
||||||
|
|
||||||
return self.playlist_result(entries, course_id, course.get('title'))
|
return self.playlist_result(
|
||||||
|
entries, course_id, metadata.get('title'),
|
||||||
|
clean_html(metadata.get('about')))
|
||||||
|
@ -168,7 +168,7 @@ class PeerTubeIE(InfoExtractor):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_peertube_url(webpage, source_url):
|
def _extract_peertube_url(webpage, source_url):
|
||||||
mobj = re.match(
|
mobj = re.match(
|
||||||
r'https?://(?P<host>[^/]+)/videos/watch/(?P<id>%s)'
|
r'https?://(?P<host>[^/]+)/videos/(?:watch|embed)/(?P<id>%s)'
|
||||||
% PeerTubeIE._UUID_RE, source_url)
|
% PeerTubeIE._UUID_RE, source_url)
|
||||||
if mobj and any(p in webpage for p in (
|
if mobj and any(p in webpage for p in (
|
||||||
'<title>PeerTube<',
|
'<title>PeerTube<',
|
||||||
|
@ -14,7 +14,7 @@ class PhilharmonieDeParisIE(InfoExtractor):
|
|||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
https?://
|
https?://
|
||||||
(?:
|
(?:
|
||||||
live\.philharmoniedeparis\.fr/(?:[Cc]oncert/|misc/Playlist\.ashx\?id=)|
|
live\.philharmoniedeparis\.fr/(?:[Cc]oncert/|embed(?:app)?/|misc/Playlist\.ashx\?id=)|
|
||||||
pad\.philharmoniedeparis\.fr/doc/CIMU/
|
pad\.philharmoniedeparis\.fr/doc/CIMU/
|
||||||
)
|
)
|
||||||
(?P<id>\d+)
|
(?P<id>\d+)
|
||||||
@ -40,6 +40,12 @@ class PhilharmonieDeParisIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://live.philharmoniedeparis.fr/misc/Playlist.ashx?id=1030324&track=&lang=fr',
|
'url': 'http://live.philharmoniedeparis.fr/misc/Playlist.ashx?id=1030324&track=&lang=fr',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://live.philharmoniedeparis.fr/embedapp/1098406/berlioz-fantastique-lelio-les-siecles-national-youth-choir-of.html?lang=fr-FR',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://live.philharmoniedeparis.fr/embed/1098406/berlioz-fantastique-lelio-les-siecles-national-youth-choir-of.html?lang=fr-FR',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
_LIVE_URL = 'https://live.philharmoniedeparis.fr'
|
_LIVE_URL = 'https://live.philharmoniedeparis.fr'
|
||||||
|
|
||||||
|
@ -50,8 +50,8 @@ class PodomaticIE(InfoExtractor):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
channel = mobj.group('channel') or mobj.group('channel_2')
|
channel = mobj.group('channel') or mobj.group('channel_2')
|
||||||
|
|
||||||
json_url = (('%s://%s.podomatic.com/entry/embed_params/%s' +
|
json_url = (('%s://%s.podomatic.com/entry/embed_params/%s'
|
||||||
'?permalink=true&rtmp=0') %
|
+ '?permalink=true&rtmp=0') %
|
||||||
(mobj.group('proto'), channel, video_id))
|
(mobj.group('proto'), channel, video_id))
|
||||||
data_json = self._download_webpage(
|
data_json = self._download_webpage(
|
||||||
json_url, video_id, 'Downloading video info')
|
json_url, video_id, 'Downloading video info')
|
||||||
|
@ -39,7 +39,12 @@ class Porn91IE(InfoExtractor):
|
|||||||
r'<div id="viewvideo-title">([^<]+)</div>', webpage, 'title')
|
r'<div id="viewvideo-title">([^<]+)</div>', webpage, 'title')
|
||||||
title = title.replace('\n', '')
|
title = title.replace('\n', '')
|
||||||
|
|
||||||
info_dict = self._parse_html5_media_entries(url, webpage, video_id)[0]
|
video_link_url = self._search_regex(
|
||||||
|
r'<textarea[^>]+id=["\']fm-video_link[^>]+>([^<]+)</textarea>',
|
||||||
|
webpage, 'video link')
|
||||||
|
videopage = self._download_webpage(video_link_url, video_id)
|
||||||
|
|
||||||
|
info_dict = self._parse_html5_media_entries(url, videopage, video_id)[0]
|
||||||
|
|
||||||
duration = parse_duration(self._search_regex(
|
duration = parse_duration(self._search_regex(
|
||||||
r'时长:\s*</span>\s*(\d+:\d+)', webpage, 'duration', fatal=False))
|
r'时长:\s*</span>\s*(\d+:\d+)', webpage, 'duration', fatal=False))
|
||||||
|
@ -1,101 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..compat import (
|
|
||||||
compat_parse_qs,
|
|
||||||
compat_str,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
|
||||||
int_or_none,
|
|
||||||
try_get,
|
|
||||||
unified_timestamp,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PornFlipIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?pornflip\.com/(?:v|embed)/(?P<id>[^/?#&]+)'
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'https://www.pornflip.com/v/wz7DfNhMmep',
|
|
||||||
'md5': '98c46639849145ae1fd77af532a9278c',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'wz7DfNhMmep',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': '2 Amateurs swallow make his dream cumshots true',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
|
||||||
'duration': 112,
|
|
||||||
'timestamp': 1481655502,
|
|
||||||
'upload_date': '20161213',
|
|
||||||
'uploader_id': '106786',
|
|
||||||
'uploader': 'figifoto',
|
|
||||||
'view_count': int,
|
|
||||||
'age_limit': 18,
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
'url': 'https://www.pornflip.com/embed/wz7DfNhMmep',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'https://www.pornflip.com/v/EkRD6-vS2-s',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'https://www.pornflip.com/embed/EkRD6-vS2-s',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'https://www.pornflip.com/v/NG9q6Pb_iK8',
|
|
||||||
'only_matching': True,
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
|
|
||||||
webpage = self._download_webpage(
|
|
||||||
'https://www.pornflip.com/v/%s' % video_id, video_id)
|
|
||||||
|
|
||||||
flashvars = compat_parse_qs(self._search_regex(
|
|
||||||
r'<embed[^>]+flashvars=(["\'])(?P<flashvars>(?:(?!\1).)+)\1',
|
|
||||||
webpage, 'flashvars', group='flashvars'))
|
|
||||||
|
|
||||||
title = flashvars['video_vars[title]'][0]
|
|
||||||
|
|
||||||
def flashvar(kind):
|
|
||||||
return try_get(
|
|
||||||
flashvars, lambda x: x['video_vars[%s]' % kind][0], compat_str)
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
for key, value in flashvars.items():
|
|
||||||
if not (value and isinstance(value, list)):
|
|
||||||
continue
|
|
||||||
format_url = value[0]
|
|
||||||
if key == 'video_vars[hds_manifest]':
|
|
||||||
formats.extend(self._extract_mpd_formats(
|
|
||||||
format_url, video_id, mpd_id='dash', fatal=False))
|
|
||||||
continue
|
|
||||||
height = self._search_regex(
|
|
||||||
r'video_vars\[video_urls\]\[(\d+)', key, 'height', default=None)
|
|
||||||
if not height:
|
|
||||||
continue
|
|
||||||
formats.append({
|
|
||||||
'url': format_url,
|
|
||||||
'format_id': 'http-%s' % height,
|
|
||||||
'height': int_or_none(height),
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
uploader = self._html_search_regex(
|
|
||||||
(r'<span[^>]+class="name"[^>]*>\s*<a[^>]+>\s*<strong>(?P<uploader>[^<]+)',
|
|
||||||
r'<meta[^>]+content=(["\'])[^>]*\buploaded by (?P<uploader>.+?)\1'),
|
|
||||||
webpage, 'uploader', fatal=False, group='uploader')
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'formats': formats,
|
|
||||||
'title': title,
|
|
||||||
'thumbnail': flashvar('big_thumb'),
|
|
||||||
'duration': int_or_none(flashvar('duration')),
|
|
||||||
'timestamp': unified_timestamp(self._html_search_meta(
|
|
||||||
'uploadDate', webpage, 'timestamp')),
|
|
||||||
'uploader_id': flashvar('author_id'),
|
|
||||||
'uploader': uploader,
|
|
||||||
'view_count': int_or_none(flashvar('views')),
|
|
||||||
'age_limit': 18,
|
|
||||||
}
|
|
@ -170,7 +170,7 @@ class PornHubIE(PornHubBaseIE):
|
|||||||
def dl_webpage(platform):
|
def dl_webpage(platform):
|
||||||
self._set_cookie(host, 'platform', platform)
|
self._set_cookie(host, 'platform', platform)
|
||||||
return self._download_webpage(
|
return self._download_webpage(
|
||||||
'http://www.%s/view_video.php?viewkey=%s' % (host, video_id),
|
'https://www.%s/view_video.php?viewkey=%s' % (host, video_id),
|
||||||
video_id, 'Downloading %s webpage' % platform)
|
video_id, 'Downloading %s webpage' % platform)
|
||||||
|
|
||||||
webpage = dl_webpage('pc')
|
webpage = dl_webpage('pc')
|
||||||
@ -372,37 +372,92 @@ class PornHubPlaylistBaseIE(PornHubBaseIE):
|
|||||||
entries, playlist_id, title, playlist.get('description'))
|
entries, playlist_id, title, playlist.get('description'))
|
||||||
|
|
||||||
|
|
||||||
class PornHubPlaylistIE(PornHubPlaylistBaseIE):
|
class PornHubUserIE(PornHubPlaylistBaseIE):
|
||||||
_VALID_URL = r'https?://(?:[^/]+\.)?(?P<host>pornhub\.(?:com|net))/playlist/(?P<id>\d+)'
|
_VALID_URL = r'(?P<url>https?://(?:[^/]+\.)?pornhub\.(?:com|net)/(?:(?:user|channel)s|model|pornstar)/(?P<id>[^/?#&]+))(?:[?#&]|/(?!videos)|$)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.pornhub.com/playlist/4667351',
|
'url': 'https://www.pornhub.com/model/zoe_ph',
|
||||||
'info_dict': {
|
'playlist_mincount': 118,
|
||||||
'id': '4667351',
|
|
||||||
'title': 'Nataly Hot',
|
|
||||||
},
|
|
||||||
'playlist_mincount': 2,
|
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://de.pornhub.com/playlist/4667351',
|
'url': 'https://www.pornhub.com/pornstar/liz-vicious',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'liz-vicious',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 118,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/users/russianveet69',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/channels/povd',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/model/zoe_ph?abc=1',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
user_id = mobj.group('id')
|
||||||
|
return self.url_result(
|
||||||
|
'%s/videos' % mobj.group('url'), ie=PornHubPagedVideoListIE.ie_key(),
|
||||||
|
video_id=user_id)
|
||||||
|
|
||||||
class PornHubUserVideosIE(PornHubPlaylistBaseIE):
|
|
||||||
_VALID_URL = r'https?://(?:[^/]+\.)?(?P<host>pornhub\.(?:com|net))/(?:(?:user|channel)s|model|pornstar)/(?P<id>[^/]+)/videos'
|
class PornHubPagedPlaylistBaseIE(PornHubPlaylistBaseIE):
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
host = mobj.group('host')
|
||||||
|
item_id = mobj.group('id')
|
||||||
|
|
||||||
|
page = int_or_none(self._search_regex(
|
||||||
|
r'\bpage=(\d+)', url, 'page', default=None))
|
||||||
|
|
||||||
|
page_url = self._make_page_url(url)
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for page_num in (page, ) if page is not None else itertools.count(1):
|
||||||
|
try:
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
page_url, item_id, 'Downloading page %d' % page_num,
|
||||||
|
query={'page': page_num})
|
||||||
|
except ExtractorError as e:
|
||||||
|
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 404:
|
||||||
|
break
|
||||||
|
raise
|
||||||
|
page_entries = self._extract_entries(webpage, host)
|
||||||
|
if not page_entries:
|
||||||
|
break
|
||||||
|
entries.extend(page_entries)
|
||||||
|
if not self._has_more(webpage):
|
||||||
|
break
|
||||||
|
|
||||||
|
return self.playlist_result(orderedSet(entries), item_id)
|
||||||
|
|
||||||
|
|
||||||
|
class PornHubPagedVideoListIE(PornHubPagedPlaylistBaseIE):
|
||||||
|
_VALID_URL = r'https?://(?:[^/]+\.)?(?P<host>pornhub\.(?:com|net))/(?P<id>(?:[^/]+/)*[^/?#&]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.pornhub.com/users/zoe_ph/videos/public',
|
'url': 'https://www.pornhub.com/model/zoe_ph/videos',
|
||||||
'info_dict': {
|
'only_matching': True,
|
||||||
'id': 'zoe_ph',
|
|
||||||
},
|
|
||||||
'playlist_mincount': 171,
|
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.pornhub.com/users/rushandlia/videos',
|
'url': 'http://www.pornhub.com/users/rushandlia/videos',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/pornstar/jenny-blighe/videos',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'pornstar/jenny-blighe/videos',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 149,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/pornstar/jenny-blighe/videos?page=3',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'pornstar/jenny-blighe/videos',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 40,
|
||||||
}, {
|
}, {
|
||||||
# default sorting as Top Rated Videos
|
# default sorting as Top Rated Videos
|
||||||
'url': 'https://www.pornhub.com/channels/povd/videos',
|
'url': 'https://www.pornhub.com/channels/povd/videos',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'povd',
|
'id': 'channels/povd/videos',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 293,
|
'playlist_mincount': 293,
|
||||||
}, {
|
}, {
|
||||||
@ -421,31 +476,107 @@ class PornHubUserVideosIE(PornHubPlaylistBaseIE):
|
|||||||
'url': 'http://www.pornhub.com/users/zoe_ph/videos/public',
|
'url': 'http://www.pornhub.com/users/zoe_ph/videos/public',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://www.pornhub.com/model/jayndrea/videos/upload',
|
# Most Viewed Videos
|
||||||
|
'url': 'https://www.pornhub.com/pornstar/liz-vicious/videos?o=mv',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://www.pornhub.com/pornstar/jenny-blighe/videos/upload',
|
# Top Rated Videos
|
||||||
|
'url': 'https://www.pornhub.com/pornstar/liz-vicious/videos?o=tr',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# Longest Videos
|
||||||
|
'url': 'https://www.pornhub.com/pornstar/liz-vicious/videos?o=lg',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# Newest Videos
|
||||||
|
'url': 'https://www.pornhub.com/pornstar/liz-vicious/videos?o=cm',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/pornstar/liz-vicious/videos/paid',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/pornstar/liz-vicious/videos/fanonly',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/video',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/video?page=3',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/video/search?search=123',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/categories/teen',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/categories/teen?page=3',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/hd',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/hd?page=3',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/described-video',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/described-video?page=2',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/video/incategories/60fps-1/hd-porn',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/playlist/44121572',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'playlist/44121572',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 132,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/playlist/4667351',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://de.pornhub.com/playlist/4667351',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
@classmethod
|
||||||
|
def suitable(cls, url):
|
||||||
|
return (False
|
||||||
|
if PornHubIE.suitable(url) or PornHubUserIE.suitable(url) or PornHubUserVideosUploadIE.suitable(url)
|
||||||
|
else super(PornHubPagedVideoListIE, cls).suitable(url))
|
||||||
|
|
||||||
|
def _make_page_url(self, url):
|
||||||
|
return url
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _has_more(webpage):
|
||||||
|
return re.search(
|
||||||
|
r'''(?x)
|
||||||
|
<li[^>]+\bclass=["\']page_next|
|
||||||
|
<link[^>]+\brel=["\']next|
|
||||||
|
<button[^>]+\bid=["\']moreDataBtn
|
||||||
|
''', webpage) is not None
|
||||||
|
|
||||||
|
|
||||||
|
class PornHubUserVideosUploadIE(PornHubPagedPlaylistBaseIE):
|
||||||
|
_VALID_URL = r'(?P<url>https?://(?:[^/]+\.)?(?P<host>pornhub\.(?:com|net))/(?:(?:user|channel)s|model|pornstar)/(?P<id>[^/]+)/videos/upload)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.pornhub.com/pornstar/jenny-blighe/videos/upload',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'jenny-blighe',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 129,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornhub.com/model/zoe_ph/videos/upload',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _make_page_url(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
host = mobj.group('host')
|
return '%s/ajax' % mobj.group('url')
|
||||||
user_id = mobj.group('id')
|
|
||||||
|
|
||||||
entries = []
|
@staticmethod
|
||||||
for page_num in itertools.count(1):
|
def _has_more(webpage):
|
||||||
try:
|
return True
|
||||||
webpage = self._download_webpage(
|
|
||||||
url, user_id, 'Downloading page %d' % page_num,
|
|
||||||
query={'page': page_num})
|
|
||||||
except ExtractorError as e:
|
|
||||||
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 404:
|
|
||||||
break
|
|
||||||
raise
|
|
||||||
page_entries = self._extract_entries(webpage, host)
|
|
||||||
if not page_entries:
|
|
||||||
break
|
|
||||||
entries.extend(page_entries)
|
|
||||||
|
|
||||||
return self.playlist_result(entries, user_id)
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user