mirror of
https://github.com/ytdl-org/youtube-dl
synced 2025-10-19 14:48:37 +09:00
Compare commits
70 Commits
2019.09.12
...
2019.10.22
Author | SHA1 | Date | |
---|---|---|---|
![]() |
820215f0e3 | ||
![]() |
b4818e3c7a | ||
![]() |
2297c0d7d9 | ||
![]() |
824fa51165 | ||
![]() |
34e3885bc9 | ||
![]() |
59296bae7e | ||
![]() |
755541a4c8 | ||
![]() |
86f63633c8 | ||
![]() |
0001157594 | ||
![]() |
bc48773ed4 | ||
![]() |
d07866f13e | ||
![]() |
2b115b9460 | ||
![]() |
e29e96a9f5 | ||
![]() |
6d394a66f5 | ||
![]() |
7815d6b743 | ||
![]() |
173190f5e3 | ||
![]() |
974311b5aa | ||
![]() |
30eb05cb41 | ||
![]() |
2af01c0293 | ||
![]() |
7e05df71b7 | ||
![]() |
a1ee23e98f | ||
![]() |
311ee45731 | ||
![]() |
c317b6163b | ||
![]() |
2765c47a8c | ||
![]() |
07b50f616e | ||
![]() |
1907f06e7b | ||
![]() |
d4bb825b83 | ||
![]() |
560d3b7d7c | ||
![]() |
4bf568d36c | ||
![]() |
05446d483d | ||
![]() |
3a37f2c3be | ||
![]() |
0b87beefe6 | ||
![]() |
fd4db1ebc2 | ||
![]() |
b64045cd2a | ||
![]() |
c2915de82e | ||
![]() |
4e72d02f39 | ||
![]() |
76e510b92c | ||
![]() |
9679a62a28 | ||
![]() |
ca20b13048 | ||
![]() |
894b3826f5 | ||
![]() |
aaf9d904aa | ||
![]() |
25e911a968 | ||
![]() |
74bc299453 | ||
![]() |
2906631e12 | ||
![]() |
326ae4ff96 | ||
![]() |
72fd4d0c6a | ||
![]() |
f4b865c613 | ||
![]() |
412f44f4b3 | ||
![]() |
6483fbd336 | ||
![]() |
8130ac42e5 | ||
![]() |
cb3e4a2947 | ||
![]() |
2a88a0c44d | ||
![]() |
33c1c7d80f | ||
![]() |
21d3c21e62 | ||
![]() |
a373befa25 | ||
![]() |
df63cafe49 | ||
![]() |
d06daf23da | ||
![]() |
8e9fdcbe27 | ||
![]() |
666d808e70 | ||
![]() |
7d327fea5b | ||
![]() |
4e3f1f0469 | ||
![]() |
4bc15a68d1 | ||
![]() |
edb2820ca5 | ||
![]() |
6cf6b357f5 | ||
![]() |
f455a934e9 | ||
![]() |
d9d3098675 | ||
![]() |
1cb812d3c2 | ||
![]() |
6fd26a7d4a | ||
![]() |
9cf26b6e1d | ||
![]() |
20e11b70ac |
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:
|
||||
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.09.12.1. 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.10.22. 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 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.
|
||||
@@ -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've verified that I'm running youtube-dl version **2019.09.12.1**
|
||||
- [ ] I've verified that I'm running youtube-dl version **2019.10.22**
|
||||
- [ ] 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 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] 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] youtube-dl version 2019.09.12.1
|
||||
[debug] youtube-dl version 2019.10.22
|
||||
[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] 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:
|
||||
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.09.12.1. 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.10.22. 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 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.
|
||||
@@ -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've verified that I'm running youtube-dl version **2019.09.12.1**
|
||||
- [ ] I've verified that I'm running youtube-dl version **2019.10.22**
|
||||
- [ ] 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 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:
|
||||
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.09.12.1. 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.10.22. 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.
|
||||
- Finally, put x into all relevant boxes (like this [x])
|
||||
-->
|
||||
|
||||
- [ ] I'm reporting a site feature request
|
||||
- [ ] I've verified that I'm running youtube-dl version **2019.09.12.1**
|
||||
- [ ] I've verified that I'm running youtube-dl version **2019.10.22**
|
||||
- [ ] 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:
|
||||
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.09.12.1. 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.10.22. 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 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.
|
||||
@@ -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've verified that I'm running youtube-dl version **2019.09.12.1**
|
||||
- [ ] I've verified that I'm running youtube-dl version **2019.10.22**
|
||||
- [ ] 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 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] 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] youtube-dl version 2019.09.12.1
|
||||
[debug] youtube-dl version 2019.10.22
|
||||
[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] 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:
|
||||
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2019.09.12.1. 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.10.22. 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.
|
||||
- Finally, put x into all relevant boxes (like this [x])
|
||||
-->
|
||||
|
||||
- [ ] I'm reporting a feature request
|
||||
- [ ] I've verified that I'm running youtube-dl version **2019.09.12.1**
|
||||
- [ ] I've verified that I'm running youtube-dl version **2019.10.22**
|
||||
- [ ] I've searched the bugtracker for similar feature requests including closed ones
|
||||
|
||||
|
||||
|
90
ChangeLog
90
ChangeLog
@@ -1,3 +1,93 @@
|
||||
version 2019.10.22
|
||||
|
||||
Core
|
||||
* [utils] Improve subtitles_filename (#22753)
|
||||
|
||||
Extractors
|
||||
* [facebook] Bypass download rate limits (#21018)
|
||||
+ [contv] Add support for contv.com
|
||||
- [viewster] Remove extractor
|
||||
* [xfileshare] Improve extractor (#17032, #17906, #18237, #18239)
|
||||
* Update the list of domains
|
||||
+ Add support for aa-encoded video data
|
||||
* Improve jwplayer format extraction
|
||||
+ Add support for Clappr sources
|
||||
* [mangomolo] Fix video format extraction and add support for player URLs
|
||||
* [audioboom] Improve metadata extraction
|
||||
* [twitch] Update VOD URL matching (#22395, #22727)
|
||||
- [mit] Remove support for video.mit.edu (#22403)
|
||||
- [servingsys] Remove extractor (#22639)
|
||||
* [dumpert] Fix extraction (#22428, #22564)
|
||||
* [atresplayer] Fix extraction (#16277, #16716)
|
||||
|
||||
|
||||
version 2019.10.16
|
||||
|
||||
Core
|
||||
* [extractor/common] Make _is_valid_url more relaxed
|
||||
|
||||
Extractors
|
||||
* [vimeo] Improve album videos id extraction (#22599)
|
||||
+ [globo] Extract subtitles (#22713)
|
||||
* [bokecc] Improve player params extraction (#22638)
|
||||
* [nexx] Handle result list (#22666)
|
||||
* [vimeo] Fix VHX embed extraction
|
||||
* [nbc] Switch to graphql API (#18581, #22693, #22701)
|
||||
- [vessel] Remove extractor
|
||||
- [promptfile] Remove extractor (#6239)
|
||||
* [kaltura] Fix service URL extraction (#22658)
|
||||
* [kaltura] Fix embed info strip (#22658)
|
||||
* [globo] Fix format extraction (#20319)
|
||||
* [redtube] Improve metadata extraction (#22492, #22615)
|
||||
* [pornhub:uservideos:upload] Fix extraction (#22619)
|
||||
+ [telequebec:squat] Add support for squat.telequebec.tv (#18503)
|
||||
- [wimp] Remove extractor (#22088, #22091)
|
||||
+ [gfycat] Extend URL regular expression (#22225)
|
||||
+ [chaturbate] Extend URL regular expression (#22309)
|
||||
* [peertube] Update instances (#22414)
|
||||
+ [telequebec] Add support for coucou.telequebec.tv (#22482)
|
||||
+ [xvideos] Extend URL regular expression (#22471)
|
||||
- [youtube] Remove support for invidious.enkirton.net (#22543)
|
||||
+ [openload] Add support for oload.monster (#22592)
|
||||
* [nrktv:seriebase] Fix extraction (#22596)
|
||||
+ [youtube] Add support for yt.lelux.fi (#22597)
|
||||
* [orf:tvthek] Make manifest requests non fatal (#22578)
|
||||
* [teachable] Skip login when already logged in (#22572)
|
||||
* [viewlift] Improve extraction (#22545)
|
||||
* [nonktube] Fix extraction (#22544)
|
||||
|
||||
|
||||
version 2019.09.28
|
||||
|
||||
Core
|
||||
* [YoutubeDL] Honour all --get-* options with --flat-playlist (#22493)
|
||||
|
||||
Extractors
|
||||
* [vk] Fix extraction (#22522)
|
||||
* [heise] Fix kaltura embeds extraction (#22514)
|
||||
* [ted] Check for resources validity and extract subtitled downloads (#22513)
|
||||
+ [youtube] Add support for
|
||||
owxfohz4kjyv25fvlqilyxast7inivgiktls3th44jhk3ej3i7ya.b32.i2p (#22292)
|
||||
+ [nhk] Add support for clips
|
||||
* [nhk] Fix video extraction (#22249, #22353)
|
||||
* [byutv] Fix extraction (#22070)
|
||||
+ [openload] Add support for oload.online (#22304)
|
||||
+ [youtube] Add support for invidious.drycat.fr (#22451)
|
||||
* [jwplatfom] Do not match video URLs (#20596, #22148)
|
||||
* [youtube:playlist] Unescape playlist uploader (#22483)
|
||||
+ [bilibili] Add support audio albums and songs (#21094)
|
||||
+ [instagram] Add support for tv URLs
|
||||
+ [mixcloud] Allow uppercase letters in format URLs (#19280)
|
||||
* [brightcove] Delegate all supported legacy URLs to new extractor (#11523,
|
||||
#12842, #13912, #15669, #16303)
|
||||
* [hotstar] Use native HLS downloader by default
|
||||
+ [hotstar] Extract more formats (#22323)
|
||||
* [9now] Fix extraction (#22361)
|
||||
* [zdf] Bypass geo restriction
|
||||
+ [tv4] Extract series metadata
|
||||
* [tv4] Fix extraction (#22443)
|
||||
|
||||
|
||||
version 2019.09.12.1
|
||||
|
||||
Extractors
|
||||
|
@@ -98,6 +98,8 @@
|
||||
- **Bigflix**
|
||||
- **Bild**: Bild.de
|
||||
- **BiliBili**
|
||||
- **BilibiliAudio**
|
||||
- **BilibiliAudioAlbum**
|
||||
- **BioBioChileTV**
|
||||
- **BIQLE**
|
||||
- **BitChute**
|
||||
@@ -181,6 +183,7 @@
|
||||
- **ComedyCentralShortname**
|
||||
- **ComedyCentralTV**
|
||||
- **CondeNast**: Condé Nast media group: Allure, Architectural Digest, Ars Technica, Bon Appétit, Brides, Condé Nast, Condé Nast Traveler, Details, Epicurious, GQ, Glamour, Golf Digest, SELF, Teen Vogue, The New Yorker, Vanity Fair, Vogue, W Magazine, WIRED
|
||||
- **CONtv**
|
||||
- **Corus**
|
||||
- **Coub**
|
||||
- **Cracked**
|
||||
@@ -692,7 +695,6 @@
|
||||
- **PornoXO**
|
||||
- **PornTube**
|
||||
- **PressTV**
|
||||
- **PromptFile**
|
||||
- **prosiebensat1**: ProSiebenSat.1 Digital
|
||||
- **puhutv**
|
||||
- **puhutv:serie**
|
||||
@@ -783,7 +785,6 @@
|
||||
- **Seeker**
|
||||
- **SenateISVP**
|
||||
- **SendtoNews**
|
||||
- **ServingSys**
|
||||
- **Servus**
|
||||
- **Sexu**
|
||||
- **SeznamZpravy**
|
||||
@@ -882,6 +883,7 @@
|
||||
- **TeleQuebec**
|
||||
- **TeleQuebecEmission**
|
||||
- **TeleQuebecLive**
|
||||
- **TeleQuebecSquat**
|
||||
- **TeleTask**
|
||||
- **Telewebion**
|
||||
- **TennisTV**
|
||||
@@ -989,7 +991,6 @@
|
||||
- **VeeHD**
|
||||
- **Veoh**
|
||||
- **verystream**
|
||||
- **Vessel**
|
||||
- **Vesti**: Вести.Ru
|
||||
- **Vevo**
|
||||
- **VevoPlaylist**
|
||||
@@ -1004,7 +1005,6 @@
|
||||
- **Viddler**
|
||||
- **Videa**
|
||||
- **video.google:search**: Google Video search
|
||||
- **video.mit.edu**
|
||||
- **VideoDetective**
|
||||
- **videofy.me**
|
||||
- **videomore**
|
||||
@@ -1022,7 +1022,6 @@
|
||||
- **vier:videos**
|
||||
- **ViewLift**
|
||||
- **ViewLiftEmbed**
|
||||
- **Viewster**
|
||||
- **Viidea**
|
||||
- **viki**
|
||||
- **viki:channel**
|
||||
@@ -1088,7 +1087,6 @@
|
||||
- **Weibo**
|
||||
- **WeiboMobile**
|
||||
- **WeiqiTV**: WQTV
|
||||
- **Wimp**
|
||||
- **Wistia**
|
||||
- **wnl**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
||||
- **WorldStarHipHop**
|
||||
@@ -1097,7 +1095,7 @@
|
||||
- **WWE**
|
||||
- **XBef**
|
||||
- **XboxClips**
|
||||
- **XFileShare**: XFileShare based sites: DaClips, FileHoot, GorillaVid, MovPod, PowerWatch, Rapidvideo.ws, TheVideoBee, Vidto, Streamin.To, XVIDSTAGE, Vid ABC, VidBom, vidlo, RapidVideo.TV, FastVideo.me
|
||||
- **XFileShare**: XFileShare based sites: ClipWatching, GoUnlimited, GoVid, HolaVid, Streamty, TheVideoBee, Uqload, VidBom, vidlo, VidLocker, VidShare, VUp, XVideoSharing
|
||||
- **XHamster**
|
||||
- **XHamsterEmbed**
|
||||
- **XHamsterUser**
|
||||
|
@@ -74,6 +74,7 @@ from youtube_dl.utils import (
|
||||
str_to_int,
|
||||
strip_jsonp,
|
||||
strip_or_none,
|
||||
subtitles_filename,
|
||||
timeconvert,
|
||||
unescapeHTML,
|
||||
unified_strdate,
|
||||
@@ -261,6 +262,11 @@ class TestUtil(unittest.TestCase):
|
||||
self.assertEqual(replace_extension('.abc', 'temp'), '.abc.temp')
|
||||
self.assertEqual(replace_extension('.abc.ext', 'temp'), '.abc.temp')
|
||||
|
||||
def test_subtitles_filename(self):
|
||||
self.assertEqual(subtitles_filename('abc.ext', 'en', 'vtt'), 'abc.en.vtt')
|
||||
self.assertEqual(subtitles_filename('abc.ext', 'en', 'vtt', 'ext'), 'abc.en.vtt')
|
||||
self.assertEqual(subtitles_filename('abc.unexpected_ext', 'en', 'vtt', 'ext'), 'abc.unexpected_ext.en.vtt')
|
||||
|
||||
def test_remove_start(self):
|
||||
self.assertEqual(remove_start(None, 'A - '), None)
|
||||
self.assertEqual(remove_start('A - B', 'A - '), 'B')
|
||||
|
@@ -852,8 +852,9 @@ class YoutubeDL(object):
|
||||
extract_flat = self.params.get('extract_flat', False)
|
||||
if ((extract_flat == 'in_playlist' and 'playlist' in extra_info)
|
||||
or extract_flat is True):
|
||||
if self.params.get('forcejson', False):
|
||||
self.to_stdout(json.dumps(ie_result))
|
||||
self.__forced_printings(
|
||||
ie_result, self.prepare_filename(ie_result),
|
||||
incomplete=True)
|
||||
return ie_result
|
||||
|
||||
if result_type == 'video':
|
||||
@@ -1693,6 +1694,36 @@ class YoutubeDL(object):
|
||||
subs[lang] = f
|
||||
return subs
|
||||
|
||||
def __forced_printings(self, info_dict, filename, incomplete):
|
||||
def print_mandatory(field):
|
||||
if (self.params.get('force%s' % field, False)
|
||||
and (not incomplete or info_dict.get(field) is not None)):
|
||||
self.to_stdout(info_dict[field])
|
||||
|
||||
def print_optional(field):
|
||||
if (self.params.get('force%s' % field, False)
|
||||
and info_dict.get(field) is not None):
|
||||
self.to_stdout(info_dict[field])
|
||||
|
||||
print_mandatory('title')
|
||||
print_mandatory('id')
|
||||
if self.params.get('forceurl', False) and not incomplete:
|
||||
if info_dict.get('requested_formats') is not None:
|
||||
for f in info_dict['requested_formats']:
|
||||
self.to_stdout(f['url'] + f.get('play_path', ''))
|
||||
else:
|
||||
# For RTMP URLs, also include the playpath
|
||||
self.to_stdout(info_dict['url'] + info_dict.get('play_path', ''))
|
||||
print_optional('thumbnail')
|
||||
print_optional('description')
|
||||
if self.params.get('forcefilename', False) and filename is not None:
|
||||
self.to_stdout(filename)
|
||||
if self.params.get('forceduration', False) and info_dict.get('duration') is not None:
|
||||
self.to_stdout(formatSeconds(info_dict['duration']))
|
||||
print_mandatory('format')
|
||||
if self.params.get('forcejson', False):
|
||||
self.to_stdout(json.dumps(info_dict))
|
||||
|
||||
def process_info(self, info_dict):
|
||||
"""Process a single resolved IE result."""
|
||||
|
||||
@@ -1703,9 +1734,8 @@ class YoutubeDL(object):
|
||||
if self._num_downloads >= int(max_downloads):
|
||||
raise MaxDownloadsReached()
|
||||
|
||||
# TODO: backward compatibility, to be removed
|
||||
info_dict['fulltitle'] = info_dict['title']
|
||||
if len(info_dict['title']) > 200:
|
||||
info_dict['title'] = info_dict['title'][:197] + '...'
|
||||
|
||||
if 'format' not in info_dict:
|
||||
info_dict['format'] = info_dict['ext']
|
||||
@@ -1720,29 +1750,7 @@ class YoutubeDL(object):
|
||||
info_dict['_filename'] = filename = self.prepare_filename(info_dict)
|
||||
|
||||
# Forced printings
|
||||
if self.params.get('forcetitle', False):
|
||||
self.to_stdout(info_dict['fulltitle'])
|
||||
if self.params.get('forceid', False):
|
||||
self.to_stdout(info_dict['id'])
|
||||
if self.params.get('forceurl', False):
|
||||
if info_dict.get('requested_formats') is not None:
|
||||
for f in info_dict['requested_formats']:
|
||||
self.to_stdout(f['url'] + f.get('play_path', ''))
|
||||
else:
|
||||
# For RTMP URLs, also include the playpath
|
||||
self.to_stdout(info_dict['url'] + info_dict.get('play_path', ''))
|
||||
if self.params.get('forcethumbnail', False) and info_dict.get('thumbnail') is not None:
|
||||
self.to_stdout(info_dict['thumbnail'])
|
||||
if self.params.get('forcedescription', False) and info_dict.get('description') is not None:
|
||||
self.to_stdout(info_dict['description'])
|
||||
if self.params.get('forcefilename', False) and filename is not None:
|
||||
self.to_stdout(filename)
|
||||
if self.params.get('forceduration', False) and info_dict.get('duration') is not None:
|
||||
self.to_stdout(formatSeconds(info_dict['duration']))
|
||||
if self.params.get('forceformat', False):
|
||||
self.to_stdout(info_dict['format'])
|
||||
if self.params.get('forcejson', False):
|
||||
self.to_stdout(json.dumps(info_dict))
|
||||
self.__forced_printings(info_dict, filename, incomplete=False)
|
||||
|
||||
# Do nothing else if in simulate mode
|
||||
if self.params.get('simulate', False):
|
||||
@@ -1806,7 +1814,7 @@ class YoutubeDL(object):
|
||||
ie = self.get_info_extractor(info_dict['extractor_key'])
|
||||
for sub_lang, sub_info in subtitles.items():
|
||||
sub_format = sub_info['ext']
|
||||
sub_filename = subtitles_filename(filename, sub_lang, sub_format)
|
||||
sub_filename = subtitles_filename(filename, sub_lang, sub_format, info_dict.get('ext'))
|
||||
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(sub_filename)):
|
||||
self.to_screen('[info] Video subtitle %s.%s is already present' % (sub_lang, sub_format))
|
||||
else:
|
||||
|
@@ -1,202 +1,118 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import time
|
||||
import hmac
|
||||
import hashlib
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..compat import compat_str
|
||||
from ..compat import compat_HTTPError
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
float_or_none,
|
||||
int_or_none,
|
||||
sanitized_Request,
|
||||
urlencode_postdata,
|
||||
xpath_text,
|
||||
)
|
||||
|
||||
|
||||
class AtresPlayerIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?atresplayer\.com/television/[^/]+/[^/]+/[^/]+/(?P<id>.+?)_\d+\.html'
|
||||
_VALID_URL = r'https?://(?:www\.)?atresplayer\.com/[^/]+/[^/]+/[^/]+/[^/]+/(?P<display_id>.+?)_(?P<id>[0-9a-f]{24})'
|
||||
_NETRC_MACHINE = 'atresplayer'
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://www.atresplayer.com/television/programas/el-club-de-la-comedia/temporada-4/capitulo-10-especial-solidario-nochebuena_2014122100174.html',
|
||||
'md5': 'efd56753cda1bb64df52a3074f62e38a',
|
||||
'url': 'https://www.atresplayer.com/antena3/series/pequenas-coincidencias/temporada-1/capitulo-7-asuntos-pendientes_5d4aa2c57ed1a88fc715a615/',
|
||||
'info_dict': {
|
||||
'id': 'capitulo-10-especial-solidario-nochebuena',
|
||||
'id': '5d4aa2c57ed1a88fc715a615',
|
||||
'ext': 'mp4',
|
||||
'title': 'Especial Solidario de Nochebuena',
|
||||
'description': 'md5:e2d52ff12214fa937107d21064075bf1',
|
||||
'duration': 5527.6,
|
||||
'thumbnail': r're:^https?://.*\.jpg$',
|
||||
'title': 'Capítulo 7: Asuntos pendientes',
|
||||
'description': 'md5:7634cdcb4d50d5381bedf93efb537fbc',
|
||||
'duration': 3413,
|
||||
},
|
||||
'params': {
|
||||
'format': 'bestvideo',
|
||||
},
|
||||
'skip': 'This video is only available for registered users'
|
||||
},
|
||||
{
|
||||
'url': 'http://www.atresplayer.com/television/especial/videoencuentros/temporada-1/capitulo-112-david-bustamante_2014121600375.html',
|
||||
'md5': '6e52cbb513c405e403dbacb7aacf8747',
|
||||
'info_dict': {
|
||||
'id': 'capitulo-112-david-bustamante',
|
||||
'ext': 'flv',
|
||||
'title': 'David Bustamante',
|
||||
'description': 'md5:f33f1c0a05be57f6708d4dd83a3b81c6',
|
||||
'duration': 1439.0,
|
||||
'thumbnail': r're:^https?://.*\.jpg$',
|
||||
},
|
||||
'url': 'https://www.atresplayer.com/lasexta/programas/el-club-de-la-comedia/temporada-4/capitulo-10-especial-solidario-nochebuena_5ad08edf986b2855ed47adc4/',
|
||||
'only_matching': True,
|
||||
},
|
||||
{
|
||||
'url': 'http://www.atresplayer.com/television/series/el-secreto-de-puente-viejo/el-chico-de-los-tres-lunares/capitulo-977-29-12-14_2014122400174.html',
|
||||
'url': 'https://www.atresplayer.com/antena3/series/el-secreto-de-puente-viejo/el-chico-de-los-tres-lunares/capitulo-977-29-12-14_5ad51046986b2886722ccdea/',
|
||||
'only_matching': True,
|
||||
},
|
||||
]
|
||||
|
||||
_USER_AGENT = 'Dalvik/1.6.0 (Linux; U; Android 4.3; GT-I9300 Build/JSS15J'
|
||||
_MAGIC = 'QWtMLXs414Yo+c#_+Q#K@NN)'
|
||||
_TIMESTAMP_SHIFT = 30000
|
||||
|
||||
_TIME_API_URL = 'http://servicios.atresplayer.com/api/admin/time.json'
|
||||
_URL_VIDEO_TEMPLATE = 'https://servicios.atresplayer.com/api/urlVideo/{1}/{0}/{1}|{2}|{3}.json'
|
||||
_PLAYER_URL_TEMPLATE = 'https://servicios.atresplayer.com/episode/getplayer.json?episodePk=%s'
|
||||
_EPISODE_URL_TEMPLATE = 'http://www.atresplayer.com/episodexml/%s'
|
||||
|
||||
_LOGIN_URL = 'https://servicios.atresplayer.com/j_spring_security_check'
|
||||
|
||||
_ERRORS = {
|
||||
'UNPUBLISHED': 'We\'re sorry, but this video is not yet available.',
|
||||
'DELETED': 'This video has expired and is no longer available for online streaming.',
|
||||
'GEOUNPUBLISHED': 'We\'re sorry, but this video is not available in your region due to right restrictions.',
|
||||
# 'PREMIUM': 'PREMIUM',
|
||||
}
|
||||
_API_BASE = 'https://api.atresplayer.com/'
|
||||
|
||||
def _real_initialize(self):
|
||||
self._login()
|
||||
|
||||
def _handle_error(self, e, code):
|
||||
if isinstance(e.cause, compat_HTTPError) and e.cause.code == code:
|
||||
error = self._parse_json(e.cause.read(), None)
|
||||
if error.get('error') == 'required_registered':
|
||||
self.raise_login_required()
|
||||
raise ExtractorError(error['error_description'], expected=True)
|
||||
raise
|
||||
|
||||
def _login(self):
|
||||
username, password = self._get_login_info()
|
||||
if username is None:
|
||||
return
|
||||
|
||||
login_form = {
|
||||
'j_username': username,
|
||||
'j_password': password,
|
||||
}
|
||||
self._request_webpage(
|
||||
self._API_BASE + 'login', None, 'Downloading login page')
|
||||
|
||||
request = sanitized_Request(
|
||||
self._LOGIN_URL, urlencode_postdata(login_form))
|
||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
response = self._download_webpage(
|
||||
request, None, 'Logging in')
|
||||
try:
|
||||
target_url = self._download_json(
|
||||
'https://account.atresmedia.com/api/login', None,
|
||||
'Logging in', headers={
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}, data=urlencode_postdata({
|
||||
'username': username,
|
||||
'password': password,
|
||||
}))['targetUrl']
|
||||
except ExtractorError as e:
|
||||
self._handle_error(e, 400)
|
||||
|
||||
error = self._html_search_regex(
|
||||
r'(?s)<ul[^>]+class="[^"]*\blist_error\b[^"]*">(.+?)</ul>',
|
||||
response, 'error', default=None)
|
||||
if error:
|
||||
raise ExtractorError(
|
||||
'Unable to login: %s' % error, expected=True)
|
||||
self._request_webpage(target_url, None, 'Following Target URL')
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
display_id, video_id = re.match(self._VALID_URL, url).groups()
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
try:
|
||||
episode = self._download_json(
|
||||
self._API_BASE + 'client/v1/player/episode/' + video_id, video_id)
|
||||
except ExtractorError as e:
|
||||
self._handle_error(e, 403)
|
||||
|
||||
episode_id = self._search_regex(
|
||||
r'episode="([^"]+)"', webpage, 'episode id')
|
||||
|
||||
request = sanitized_Request(
|
||||
self._PLAYER_URL_TEMPLATE % episode_id,
|
||||
headers={'User-Agent': self._USER_AGENT})
|
||||
player = self._download_json(request, episode_id, 'Downloading player JSON')
|
||||
|
||||
episode_type = player.get('typeOfEpisode')
|
||||
error_message = self._ERRORS.get(episode_type)
|
||||
if error_message:
|
||||
raise ExtractorError(
|
||||
'%s returned error: %s' % (self.IE_NAME, error_message), expected=True)
|
||||
title = episode['titulo']
|
||||
|
||||
formats = []
|
||||
video_url = player.get('urlVideo')
|
||||
if video_url:
|
||||
format_info = {
|
||||
'url': video_url,
|
||||
'format_id': 'http',
|
||||
}
|
||||
mobj = re.search(r'(?P<bitrate>\d+)K_(?P<width>\d+)x(?P<height>\d+)', video_url)
|
||||
if mobj:
|
||||
format_info.update({
|
||||
'width': int_or_none(mobj.group('width')),
|
||||
'height': int_or_none(mobj.group('height')),
|
||||
'tbr': int_or_none(mobj.group('bitrate')),
|
||||
})
|
||||
formats.append(format_info)
|
||||
|
||||
timestamp = int_or_none(self._download_webpage(
|
||||
self._TIME_API_URL,
|
||||
video_id, 'Downloading timestamp', fatal=False), 1000, time.time())
|
||||
timestamp_shifted = compat_str(timestamp + self._TIMESTAMP_SHIFT)
|
||||
token = hmac.new(
|
||||
self._MAGIC.encode('ascii'),
|
||||
(episode_id + timestamp_shifted).encode('utf-8'), hashlib.md5
|
||||
).hexdigest()
|
||||
|
||||
request = sanitized_Request(
|
||||
self._URL_VIDEO_TEMPLATE.format('windows', episode_id, timestamp_shifted, token),
|
||||
headers={'User-Agent': self._USER_AGENT})
|
||||
|
||||
fmt_json = self._download_json(
|
||||
request, video_id, 'Downloading windows video JSON')
|
||||
|
||||
result = fmt_json.get('resultDes')
|
||||
if result.lower() != 'ok':
|
||||
raise ExtractorError(
|
||||
'%s returned error: %s' % (self.IE_NAME, result), expected=True)
|
||||
|
||||
for format_id, video_url in fmt_json['resultObject'].items():
|
||||
if format_id == 'token' or not video_url.startswith('http'):
|
||||
for source in episode.get('sources', []):
|
||||
src = source.get('src')
|
||||
if not src:
|
||||
continue
|
||||
if 'geodeswowsmpra3player' in video_url:
|
||||
# f4m_path = video_url.split('smil:', 1)[-1].split('free_', 1)[0]
|
||||
# f4m_url = 'http://drg.antena3.com/{0}hds/es/sd.f4m'.format(f4m_path)
|
||||
# this videos are protected by DRM, the f4m downloader doesn't support them
|
||||
continue
|
||||
video_url_hd = video_url.replace('free_es', 'es')
|
||||
formats.extend(self._extract_f4m_formats(
|
||||
video_url_hd[:-9] + '/manifest.f4m', video_id, f4m_id='hds',
|
||||
fatal=False))
|
||||
formats.extend(self._extract_mpd_formats(
|
||||
video_url_hd[:-9] + '/manifest.mpd', video_id, mpd_id='dash',
|
||||
fatal=False))
|
||||
src_type = source.get('type')
|
||||
if src_type == 'application/vnd.apple.mpegurl':
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
src, video_id, 'mp4', 'm3u8_native',
|
||||
m3u8_id='hls', fatal=False))
|
||||
elif src_type == 'application/dash+xml':
|
||||
formats.extend(self._extract_mpd_formats(
|
||||
src, video_id, mpd_id='dash', fatal=False))
|
||||
self._sort_formats(formats)
|
||||
|
||||
path_data = player.get('pathData')
|
||||
|
||||
episode = self._download_xml(
|
||||
self._EPISODE_URL_TEMPLATE % path_data, video_id,
|
||||
'Downloading episode XML')
|
||||
|
||||
duration = float_or_none(xpath_text(
|
||||
episode, './media/asset/info/technical/contentDuration', 'duration'))
|
||||
|
||||
art = episode.find('./media/asset/info/art')
|
||||
title = xpath_text(art, './name', 'title')
|
||||
description = xpath_text(art, './description', 'description')
|
||||
thumbnail = xpath_text(episode, './media/asset/files/background', 'thumbnail')
|
||||
|
||||
subtitles = {}
|
||||
subtitle_url = xpath_text(episode, './media/asset/files/subtitle', 'subtitle')
|
||||
if subtitle_url:
|
||||
subtitles['es'] = [{
|
||||
'ext': 'srt',
|
||||
'url': subtitle_url,
|
||||
}]
|
||||
heartbeat = episode.get('heartbeat') or {}
|
||||
omniture = episode.get('omniture') or {}
|
||||
get_meta = lambda x: heartbeat.get(x) or omniture.get(x)
|
||||
|
||||
return {
|
||||
'display_id': display_id,
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'thumbnail': thumbnail,
|
||||
'duration': duration,
|
||||
'description': episode.get('descripcion'),
|
||||
'thumbnail': episode.get('imgPoster'),
|
||||
'duration': int_or_none(episode.get('duration')),
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
'channel': get_meta('channel'),
|
||||
'season': get_meta('season'),
|
||||
'episode_number': int_or_none(get_meta('episodeNumber')),
|
||||
}
|
||||
|
@@ -2,22 +2,25 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import float_or_none
|
||||
from ..utils import (
|
||||
clean_html,
|
||||
float_or_none,
|
||||
)
|
||||
|
||||
|
||||
class AudioBoomIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?audioboom\.com/(?:boos|posts)/(?P<id>[0-9]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://audioboom.com/boos/4279833-3-09-2016-czaban-hour-3?t=0',
|
||||
'md5': '63a8d73a055c6ed0f1e51921a10a5a76',
|
||||
'url': 'https://audioboom.com/posts/7398103-asim-chaudhry',
|
||||
'md5': '7b00192e593ff227e6a315486979a42d',
|
||||
'info_dict': {
|
||||
'id': '4279833',
|
||||
'id': '7398103',
|
||||
'ext': 'mp3',
|
||||
'title': '3/09/2016 Czaban Hour 3',
|
||||
'description': 'Guest: Nate Davis - NFL free agency, Guest: Stan Gans',
|
||||
'duration': 2245.72,
|
||||
'uploader': 'SB Nation A.M.',
|
||||
'uploader_url': r're:https?://(?:www\.)?audioboom\.com/channel/steveczabanyahoosportsradio',
|
||||
'title': 'Asim Chaudhry',
|
||||
'description': 'md5:2f3fef17dacc2595b5362e1d7d3602fc',
|
||||
'duration': 4000.99,
|
||||
'uploader': 'Sue Perkins: An hour or so with...',
|
||||
'uploader_url': r're:https?://(?:www\.)?audioboom\.com/channel/perkins',
|
||||
}
|
||||
}, {
|
||||
'url': 'https://audioboom.com/posts/4279833-3-09-2016-czaban-hour-3?t=0',
|
||||
@@ -32,8 +35,8 @@ class AudioBoomIE(InfoExtractor):
|
||||
clip = None
|
||||
|
||||
clip_store = self._parse_json(
|
||||
self._search_regex(
|
||||
r'data-new-clip-store=(["\'])(?P<json>{.*?"clipId"\s*:\s*%s.*?})\1' % video_id,
|
||||
self._html_search_regex(
|
||||
r'data-new-clip-store=(["\'])(?P<json>{.+?})\1',
|
||||
webpage, 'clip store', default='{}', group='json'),
|
||||
video_id, fatal=False)
|
||||
if clip_store:
|
||||
@@ -47,14 +50,15 @@ class AudioBoomIE(InfoExtractor):
|
||||
|
||||
audio_url = from_clip('clipURLPriorToLoading') or self._og_search_property(
|
||||
'audio', webpage, 'audio url')
|
||||
title = from_clip('title') or self._og_search_title(webpage)
|
||||
description = from_clip('description') or self._og_search_description(webpage)
|
||||
title = from_clip('title') or self._html_search_meta(
|
||||
['og:title', 'og:audio:title', 'audio_title'], webpage)
|
||||
description = from_clip('description') or clean_html(from_clip('formattedDescription')) or self._og_search_description(webpage)
|
||||
|
||||
duration = float_or_none(from_clip('duration') or self._html_search_meta(
|
||||
'weibo:audio:duration', webpage))
|
||||
|
||||
uploader = from_clip('author') or self._og_search_property(
|
||||
'audio:artist', webpage, 'uploader', fatal=False)
|
||||
uploader = from_clip('author') or self._html_search_meta(
|
||||
['og:audio:artist', 'twitter:audio:artist_name', 'audio_artist'], webpage, 'uploader')
|
||||
uploader_url = from_clip('author_url') or self._html_search_meta(
|
||||
'audioboo:channel', webpage, 'uploader url')
|
||||
|
||||
|
@@ -15,6 +15,7 @@ from ..utils import (
|
||||
float_or_none,
|
||||
parse_iso8601,
|
||||
smuggle_url,
|
||||
str_or_none,
|
||||
strip_jsonp,
|
||||
unified_timestamp,
|
||||
unsmuggle_url,
|
||||
@@ -306,3 +307,115 @@ class BiliBiliBangumiIE(InfoExtractor):
|
||||
return self.playlist_result(
|
||||
entries, bangumi_id,
|
||||
season_info.get('bangumi_title'), season_info.get('evaluate'))
|
||||
|
||||
|
||||
class BilibiliAudioBaseIE(InfoExtractor):
|
||||
def _call_api(self, path, sid, query=None):
|
||||
if not query:
|
||||
query = {'sid': sid}
|
||||
return self._download_json(
|
||||
'https://www.bilibili.com/audio/music-service-c/web/' + path,
|
||||
sid, query=query)['data']
|
||||
|
||||
|
||||
class BilibiliAudioIE(BilibiliAudioBaseIE):
|
||||
_VALID_URL = r'https?://(?:www\.)?bilibili\.com/audio/au(?P<id>\d+)'
|
||||
_TEST = {
|
||||
'url': 'https://www.bilibili.com/audio/au1003142',
|
||||
'md5': 'fec4987014ec94ef9e666d4d158ad03b',
|
||||
'info_dict': {
|
||||
'id': '1003142',
|
||||
'ext': 'm4a',
|
||||
'title': '【tsukimi】YELLOW / 神山羊',
|
||||
'artist': 'tsukimi',
|
||||
'comment_count': int,
|
||||
'description': 'YELLOW的mp3版!',
|
||||
'duration': 183,
|
||||
'subtitles': {
|
||||
'origin': [{
|
||||
'ext': 'lrc',
|
||||
}],
|
||||
},
|
||||
'thumbnail': r're:^https?://.+\.jpg',
|
||||
'timestamp': 1564836614,
|
||||
'upload_date': '20190803',
|
||||
'uploader': 'tsukimi-つきみぐー',
|
||||
'view_count': int,
|
||||
},
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
au_id = self._match_id(url)
|
||||
|
||||
play_data = self._call_api('url', au_id)
|
||||
formats = [{
|
||||
'url': play_data['cdns'][0],
|
||||
'filesize': int_or_none(play_data.get('size')),
|
||||
}]
|
||||
|
||||
song = self._call_api('song/info', au_id)
|
||||
title = song['title']
|
||||
statistic = song.get('statistic') or {}
|
||||
|
||||
subtitles = None
|
||||
lyric = song.get('lyric')
|
||||
if lyric:
|
||||
subtitles = {
|
||||
'origin': [{
|
||||
'url': lyric,
|
||||
}]
|
||||
}
|
||||
|
||||
return {
|
||||
'id': au_id,
|
||||
'title': title,
|
||||
'formats': formats,
|
||||
'artist': song.get('author'),
|
||||
'comment_count': int_or_none(statistic.get('comment')),
|
||||
'description': song.get('intro'),
|
||||
'duration': int_or_none(song.get('duration')),
|
||||
'subtitles': subtitles,
|
||||
'thumbnail': song.get('cover'),
|
||||
'timestamp': int_or_none(song.get('passtime')),
|
||||
'uploader': song.get('uname'),
|
||||
'view_count': int_or_none(statistic.get('play')),
|
||||
}
|
||||
|
||||
|
||||
class BilibiliAudioAlbumIE(BilibiliAudioBaseIE):
|
||||
_VALID_URL = r'https?://(?:www\.)?bilibili\.com/audio/am(?P<id>\d+)'
|
||||
_TEST = {
|
||||
'url': 'https://www.bilibili.com/audio/am10624',
|
||||
'info_dict': {
|
||||
'id': '10624',
|
||||
'title': '每日新曲推荐(每日11:00更新)',
|
||||
'description': '每天11:00更新,为你推送最新音乐',
|
||||
},
|
||||
'playlist_count': 19,
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
am_id = self._match_id(url)
|
||||
|
||||
songs = self._call_api(
|
||||
'song/of-menu', am_id, {'sid': am_id, 'pn': 1, 'ps': 100})['data']
|
||||
|
||||
entries = []
|
||||
for song in songs:
|
||||
sid = str_or_none(song.get('id'))
|
||||
if not sid:
|
||||
continue
|
||||
entries.append(self.url_result(
|
||||
'https://www.bilibili.com/audio/au' + sid,
|
||||
BilibiliAudioIE.ie_key(), sid))
|
||||
|
||||
if entries:
|
||||
album_data = self._call_api('menu/info', am_id) or {}
|
||||
album_title = album_data.get('title')
|
||||
if album_title:
|
||||
for entry in entries:
|
||||
entry['album'] = album_title
|
||||
return self.playlist_result(
|
||||
entries, am_id, album_title, album_data.get('intro'))
|
||||
|
||||
return self.playlist_result(entries, am_id)
|
||||
|
@@ -11,8 +11,8 @@ from ..utils import ExtractorError
|
||||
class BokeCCBaseIE(InfoExtractor):
|
||||
def _extract_bokecc_formats(self, webpage, video_id, format_id=None):
|
||||
player_params_str = self._html_search_regex(
|
||||
r'<(?:script|embed)[^>]+src="http://p\.bokecc\.com/player\?([^"]+)',
|
||||
webpage, 'player params')
|
||||
r'<(?:script|embed)[^>]+src=(?P<q>["\'])(?:https?:)?//p\.bokecc\.com/(?:player|flash/player\.swf)\?(?P<query>.+?)(?P=q)',
|
||||
webpage, 'player params', group='query')
|
||||
|
||||
player_params = compat_parse_qs(player_params_str)
|
||||
|
||||
@@ -36,9 +36,9 @@ class BokeCCIE(BokeCCBaseIE):
|
||||
_VALID_URL = r'https?://union\.bokecc\.com/playvideo\.bo\?(?P<query>.*)'
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'http://union.bokecc.com/playvideo.bo?vid=E44D40C15E65EA30&uid=CD0C5D3C8614B28B',
|
||||
'url': 'http://union.bokecc.com/playvideo.bo?vid=E0ABAE9D4F509B189C33DC5901307461&uid=FE644790DE9D154A',
|
||||
'info_dict': {
|
||||
'id': 'CD0C5D3C8614B28B_E44D40C15E65EA30',
|
||||
'id': 'FE644790DE9D154A_E0ABAE9D4F509B189C33DC5901307461',
|
||||
'ext': 'flv',
|
||||
'title': 'BokeCC Video',
|
||||
},
|
||||
|
@@ -2,7 +2,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import base64
|
||||
import json
|
||||
import re
|
||||
import struct
|
||||
|
||||
@@ -11,14 +10,12 @@ from .adobepass import AdobePassIE
|
||||
from ..compat import (
|
||||
compat_etree_fromstring,
|
||||
compat_parse_qs,
|
||||
compat_str,
|
||||
compat_urllib_parse_urlparse,
|
||||
compat_urlparse,
|
||||
compat_xml_parse_error,
|
||||
compat_HTTPError,
|
||||
)
|
||||
from ..utils import (
|
||||
determine_ext,
|
||||
ExtractorError,
|
||||
extract_attributes,
|
||||
find_xpath_attr,
|
||||
@@ -27,18 +24,19 @@ from ..utils import (
|
||||
js_to_json,
|
||||
int_or_none,
|
||||
parse_iso8601,
|
||||
smuggle_url,
|
||||
unescapeHTML,
|
||||
unsmuggle_url,
|
||||
update_url_query,
|
||||
clean_html,
|
||||
mimetype2ext,
|
||||
UnsupportedError,
|
||||
)
|
||||
|
||||
|
||||
class BrightcoveLegacyIE(InfoExtractor):
|
||||
IE_NAME = 'brightcove:legacy'
|
||||
_VALID_URL = r'(?:https?://.*brightcove\.com/(services|viewer).*?\?|brightcove:)(?P<query>.*)'
|
||||
_FEDERATED_URL = 'http://c.brightcove.com/services/viewer/htmlFederated'
|
||||
|
||||
_TESTS = [
|
||||
{
|
||||
@@ -55,7 +53,8 @@ class BrightcoveLegacyIE(InfoExtractor):
|
||||
'timestamp': 1368213670,
|
||||
'upload_date': '20130510',
|
||||
'uploader_id': '1589608506001',
|
||||
}
|
||||
},
|
||||
'skip': 'The player has been deactivated by the content owner',
|
||||
},
|
||||
{
|
||||
# From http://medianetwork.oracle.com/video/player/1785452137001
|
||||
@@ -70,6 +69,7 @@ class BrightcoveLegacyIE(InfoExtractor):
|
||||
'upload_date': '20120814',
|
||||
'uploader_id': '1460825906',
|
||||
},
|
||||
'skip': 'video not playable',
|
||||
},
|
||||
{
|
||||
# From http://mashable.com/2013/10/26/thermoelectric-bracelet-lets-you-control-your-body-temperature/
|
||||
@@ -79,7 +79,7 @@ class BrightcoveLegacyIE(InfoExtractor):
|
||||
'ext': 'mp4',
|
||||
'title': 'This Bracelet Acts as a Personal Thermostat',
|
||||
'description': 'md5:547b78c64f4112766ccf4e151c20b6a0',
|
||||
'uploader': 'Mashable',
|
||||
# 'uploader': 'Mashable',
|
||||
'timestamp': 1382041798,
|
||||
'upload_date': '20131017',
|
||||
'uploader_id': '1130468786001',
|
||||
@@ -124,6 +124,7 @@ class BrightcoveLegacyIE(InfoExtractor):
|
||||
'id': '3550319591001',
|
||||
},
|
||||
'playlist_mincount': 7,
|
||||
'skip': 'Unsupported URL',
|
||||
},
|
||||
{
|
||||
# playlist with 'playlistTab' (https://github.com/ytdl-org/youtube-dl/issues/9965)
|
||||
@@ -133,6 +134,7 @@ class BrightcoveLegacyIE(InfoExtractor):
|
||||
'title': 'Lesson 08',
|
||||
},
|
||||
'playlist_mincount': 10,
|
||||
'skip': 'Unsupported URL',
|
||||
},
|
||||
{
|
||||
# playerID inferred from bcpid
|
||||
@@ -141,12 +143,6 @@ class BrightcoveLegacyIE(InfoExtractor):
|
||||
'only_matching': True, # Tested in GenericIE
|
||||
}
|
||||
]
|
||||
FLV_VCODECS = {
|
||||
1: 'SORENSON',
|
||||
2: 'ON2',
|
||||
3: 'H264',
|
||||
4: 'VP8',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _build_brighcove_url(cls, object_str):
|
||||
@@ -238,7 +234,8 @@ class BrightcoveLegacyIE(InfoExtractor):
|
||||
|
||||
@classmethod
|
||||
def _make_brightcove_url(cls, params):
|
||||
return update_url_query(cls._FEDERATED_URL, params)
|
||||
return update_url_query(
|
||||
'http://c.brightcove.com/services/viewer/htmlFederated', params)
|
||||
|
||||
@classmethod
|
||||
def _extract_brightcove_url(cls, webpage):
|
||||
@@ -297,38 +294,12 @@ class BrightcoveLegacyIE(InfoExtractor):
|
||||
videoPlayer = query.get('@videoPlayer')
|
||||
if videoPlayer:
|
||||
# We set the original url as the default 'Referer' header
|
||||
referer = smuggled_data.get('Referer', url)
|
||||
referer = query.get('linkBaseURL', [None])[0] or smuggled_data.get('Referer', url)
|
||||
video_id = videoPlayer[0]
|
||||
if 'playerID' not in query:
|
||||
mobj = re.search(r'/bcpid(\d+)', url)
|
||||
if mobj is not None:
|
||||
query['playerID'] = [mobj.group(1)]
|
||||
return self._get_video_info(
|
||||
videoPlayer[0], query, referer=referer)
|
||||
elif 'playerKey' in query:
|
||||
player_key = query['playerKey']
|
||||
return self._get_playlist_info(player_key[0])
|
||||
else:
|
||||
raise ExtractorError(
|
||||
'Cannot find playerKey= variable. Did you forget quotes in a shell invocation?',
|
||||
expected=True)
|
||||
|
||||
def _brightcove_new_url_result(self, publisher_id, video_id):
|
||||
brightcove_new_url = 'http://players.brightcove.net/%s/default_default/index.html?videoId=%s' % (publisher_id, video_id)
|
||||
return self.url_result(brightcove_new_url, BrightcoveNewIE.ie_key(), video_id)
|
||||
|
||||
def _get_video_info(self, video_id, query, referer=None):
|
||||
headers = {}
|
||||
linkBase = query.get('linkBaseURL')
|
||||
if linkBase is not None:
|
||||
referer = linkBase[0]
|
||||
if referer is not None:
|
||||
headers['Referer'] = referer
|
||||
webpage = self._download_webpage(self._FEDERATED_URL, video_id, headers=headers, query=query)
|
||||
|
||||
error_msg = self._html_search_regex(
|
||||
r"<h1>We're sorry.</h1>([\s\n]*<p>.*?</p>)+", webpage,
|
||||
'error message', default=None)
|
||||
if error_msg is not None:
|
||||
publisher_id = query.get('publisherId')
|
||||
if publisher_id and publisher_id[0].isdigit():
|
||||
publisher_id = publisher_id[0]
|
||||
@@ -339,6 +310,9 @@ class BrightcoveLegacyIE(InfoExtractor):
|
||||
else:
|
||||
player_id = query.get('playerID')
|
||||
if player_id and player_id[0].isdigit():
|
||||
headers = {}
|
||||
if referer:
|
||||
headers['Referer'] = referer
|
||||
player_page = self._download_webpage(
|
||||
'http://link.brightcove.com/services/player/bcpid' + player_id[0],
|
||||
video_id, headers=headers, fatal=False)
|
||||
@@ -349,136 +323,16 @@ class BrightcoveLegacyIE(InfoExtractor):
|
||||
if player_key:
|
||||
enc_pub_id = player_key.split(',')[1].replace('~', '=')
|
||||
publisher_id = struct.unpack('>Q', base64.urlsafe_b64decode(enc_pub_id))[0]
|
||||
if publisher_id:
|
||||
return self._brightcove_new_url_result(publisher_id, video_id)
|
||||
raise ExtractorError(
|
||||
'brightcove said: %s' % error_msg, expected=True)
|
||||
|
||||
self.report_extraction(video_id)
|
||||
info = self._search_regex(r'var experienceJSON = ({.*});', webpage, 'json')
|
||||
info = json.loads(info)['data']
|
||||
video_info = info['programmedContent']['videoPlayer']['mediaDTO']
|
||||
video_info['_youtubedl_adServerURL'] = info.get('adServerURL')
|
||||
|
||||
return self._extract_video_info(video_info)
|
||||
|
||||
def _get_playlist_info(self, player_key):
|
||||
info_url = 'http://c.brightcove.com/services/json/experience/runtime/?command=get_programming_for_experience&playerKey=%s' % player_key
|
||||
playlist_info = self._download_webpage(
|
||||
info_url, player_key, 'Downloading playlist information')
|
||||
|
||||
json_data = json.loads(playlist_info)
|
||||
if 'videoList' in json_data:
|
||||
playlist_info = json_data['videoList']
|
||||
playlist_dto = playlist_info['mediaCollectionDTO']
|
||||
elif 'playlistTabs' in json_data:
|
||||
playlist_info = json_data['playlistTabs']
|
||||
playlist_dto = playlist_info['lineupListDTO']['playlistDTOs'][0]
|
||||
else:
|
||||
raise ExtractorError('Empty playlist')
|
||||
|
||||
videos = [self._extract_video_info(video_info) for video_info in playlist_dto['videoDTOs']]
|
||||
|
||||
return self.playlist_result(videos, playlist_id='%s' % playlist_info['id'],
|
||||
playlist_title=playlist_dto['displayName'])
|
||||
|
||||
def _extract_video_info(self, video_info):
|
||||
video_id = compat_str(video_info['id'])
|
||||
publisher_id = video_info.get('publisherId')
|
||||
info = {
|
||||
'id': video_id,
|
||||
'title': video_info['displayName'].strip(),
|
||||
'description': video_info.get('shortDescription'),
|
||||
'thumbnail': video_info.get('videoStillURL') or video_info.get('thumbnailURL'),
|
||||
'uploader': video_info.get('publisherName'),
|
||||
'uploader_id': compat_str(publisher_id) if publisher_id else None,
|
||||
'duration': float_or_none(video_info.get('length'), 1000),
|
||||
'timestamp': int_or_none(video_info.get('creationDate'), 1000),
|
||||
}
|
||||
|
||||
renditions = video_info.get('renditions', []) + video_info.get('IOSRenditions', [])
|
||||
if renditions:
|
||||
formats = []
|
||||
for rend in renditions:
|
||||
url = rend['defaultURL']
|
||||
if not url:
|
||||
continue
|
||||
ext = None
|
||||
if rend['remote']:
|
||||
url_comp = compat_urllib_parse_urlparse(url)
|
||||
if url_comp.path.endswith('.m3u8'):
|
||||
formats.extend(
|
||||
self._extract_m3u8_formats(
|
||||
url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
|
||||
continue
|
||||
elif 'akamaihd.net' in url_comp.netloc:
|
||||
# This type of renditions are served through
|
||||
# akamaihd.net, but they don't use f4m manifests
|
||||
url = url.replace('control/', '') + '?&v=3.3.0&fp=13&r=FEEFJ&g=RTSJIMBMPFPB'
|
||||
ext = 'flv'
|
||||
if ext is None:
|
||||
ext = determine_ext(url)
|
||||
tbr = int_or_none(rend.get('encodingRate'), 1000)
|
||||
a_format = {
|
||||
'format_id': 'http%s' % ('-%s' % tbr if tbr else ''),
|
||||
'url': url,
|
||||
'ext': ext,
|
||||
'filesize': int_or_none(rend.get('size')) or None,
|
||||
'tbr': tbr,
|
||||
}
|
||||
if rend.get('audioOnly'):
|
||||
a_format.update({
|
||||
'vcodec': 'none',
|
||||
})
|
||||
else:
|
||||
a_format.update({
|
||||
'height': int_or_none(rend.get('frameHeight')),
|
||||
'width': int_or_none(rend.get('frameWidth')),
|
||||
'vcodec': rend.get('videoCodec'),
|
||||
})
|
||||
|
||||
# m3u8 manifests with remote == false are media playlists
|
||||
# Not calling _extract_m3u8_formats here to save network traffic
|
||||
if ext == 'm3u8':
|
||||
a_format.update({
|
||||
'format_id': 'hls%s' % ('-%s' % tbr if tbr else ''),
|
||||
'ext': 'mp4',
|
||||
'protocol': 'm3u8_native',
|
||||
})
|
||||
|
||||
formats.append(a_format)
|
||||
self._sort_formats(formats)
|
||||
info['formats'] = formats
|
||||
elif video_info.get('FLVFullLengthURL') is not None:
|
||||
info.update({
|
||||
'url': video_info['FLVFullLengthURL'],
|
||||
'vcodec': self.FLV_VCODECS.get(video_info.get('FLVFullCodec')),
|
||||
'filesize': int_or_none(video_info.get('FLVFullSize')),
|
||||
})
|
||||
|
||||
if self._downloader.params.get('include_ads', False):
|
||||
adServerURL = video_info.get('_youtubedl_adServerURL')
|
||||
if adServerURL:
|
||||
ad_info = {
|
||||
'_type': 'url',
|
||||
'url': adServerURL,
|
||||
}
|
||||
if 'url' in info:
|
||||
return {
|
||||
'_type': 'playlist',
|
||||
'title': info['title'],
|
||||
'entries': [ad_info, info],
|
||||
}
|
||||
else:
|
||||
return ad_info
|
||||
|
||||
if not info.get('url') and not info.get('formats'):
|
||||
uploader_id = info.get('uploader_id')
|
||||
if uploader_id:
|
||||
info.update(self._brightcove_new_url_result(uploader_id, video_id))
|
||||
else:
|
||||
raise ExtractorError('Unable to extract video url for %s' % video_id)
|
||||
return info
|
||||
if publisher_id:
|
||||
brightcove_new_url = 'http://players.brightcove.net/%s/default_default/index.html?videoId=%s' % (publisher_id, video_id)
|
||||
if referer:
|
||||
brightcove_new_url = smuggle_url(brightcove_new_url, {'referrer': referer})
|
||||
return self.url_result(brightcove_new_url, BrightcoveNewIE.ie_key(), video_id)
|
||||
# TODO: figure out if it's possible to extract playlistId from playerKey
|
||||
# elif 'playerKey' in query:
|
||||
# player_key = query['playerKey']
|
||||
# return self._get_playlist_info(player_key[0])
|
||||
raise UnsupportedError(url)
|
||||
|
||||
|
||||
class BrightcoveNewIE(AdobePassIE):
|
||||
|
@@ -3,7 +3,12 @@ from __future__ import unicode_literals
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import parse_duration
|
||||
from ..utils import (
|
||||
determine_ext,
|
||||
merge_dicts,
|
||||
parse_duration,
|
||||
url_or_none,
|
||||
)
|
||||
|
||||
|
||||
class BYUtvIE(InfoExtractor):
|
||||
@@ -51,7 +56,7 @@ class BYUtvIE(InfoExtractor):
|
||||
video_id = mobj.group('id')
|
||||
display_id = mobj.group('display_id') or video_id
|
||||
|
||||
info = self._download_json(
|
||||
video = self._download_json(
|
||||
'https://api.byutv.org/api3/catalog/getvideosforcontent',
|
||||
display_id, query={
|
||||
'contentid': video_id,
|
||||
@@ -62,7 +67,7 @@ class BYUtvIE(InfoExtractor):
|
||||
'x-byutv-platformkey': 'xsaaw9c7y5',
|
||||
})
|
||||
|
||||
ep = info.get('ooyalaVOD')
|
||||
ep = video.get('ooyalaVOD')
|
||||
if ep:
|
||||
return {
|
||||
'_type': 'url_transparent',
|
||||
@@ -75,18 +80,38 @@ class BYUtvIE(InfoExtractor):
|
||||
'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')
|
||||
info = {}
|
||||
formats = []
|
||||
for format_id, ep in video.items():
|
||||
if not isinstance(ep, dict):
|
||||
continue
|
||||
video_url = url_or_none(ep.get('videoUrl'))
|
||||
if not video_url:
|
||||
continue
|
||||
ext = determine_ext(video_url)
|
||||
if ext == 'm3u8':
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
video_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||
m3u8_id='hls', fatal=False))
|
||||
elif ext == 'mpd':
|
||||
formats.extend(self._extract_mpd_formats(
|
||||
video_url, video_id, mpd_id='dash', fatal=False))
|
||||
else:
|
||||
formats.append({
|
||||
'url': video_url,
|
||||
'format_id': format_id,
|
||||
})
|
||||
merge_dicts(info, {
|
||||
'title': ep.get('title'),
|
||||
'description': ep.get('description'),
|
||||
'thumbnail': ep.get('imageThumbnail'),
|
||||
'duration': parse_duration(ep.get('length')),
|
||||
})
|
||||
self._sort_formats(formats)
|
||||
return {
|
||||
|
||||
return merge_dicts(info, {
|
||||
'id': video_id,
|
||||
'display_id': display_id,
|
||||
'title': title,
|
||||
'description': ep.get('description'),
|
||||
'thumbnail': ep.get('imageThumbnail'),
|
||||
'duration': parse_duration(ep.get('length')),
|
||||
'title': display_id,
|
||||
'formats': formats,
|
||||
}
|
||||
})
|
||||
|
@@ -7,7 +7,7 @@ from ..utils import ExtractorError
|
||||
|
||||
|
||||
class ChaturbateIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:[^/]+\.)?chaturbate\.com/(?P<id>[^/?#]+)'
|
||||
_VALID_URL = r'https?://(?:[^/]+\.)?chaturbate\.com/(?:fullvideo/?\?.*?\bb=)?(?P<id>[^/?&#]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.chaturbate.com/siswet19/',
|
||||
'info_dict': {
|
||||
@@ -21,6 +21,9 @@ class ChaturbateIE(InfoExtractor):
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'Room is offline',
|
||||
}, {
|
||||
'url': 'https://chaturbate.com/fullvideo/?b=caylin',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://en.chaturbate.com/siswet19/',
|
||||
'only_matching': True,
|
||||
@@ -32,7 +35,8 @@ class ChaturbateIE(InfoExtractor):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
webpage = self._download_webpage(
|
||||
url, video_id, headers=self.geo_verification_headers())
|
||||
'https://chaturbate.com/%s/' % video_id, video_id,
|
||||
headers=self.geo_verification_headers())
|
||||
|
||||
m3u8_urls = []
|
||||
|
||||
|
@@ -1424,12 +1424,10 @@ class InfoExtractor(object):
|
||||
try:
|
||||
self._request_webpage(url, video_id, 'Checking %s URL' % item, headers=headers)
|
||||
return True
|
||||
except ExtractorError as e:
|
||||
if isinstance(e.cause, compat_urllib_error.URLError):
|
||||
self.to_screen(
|
||||
'%s: %s URL is invalid, skipping' % (video_id, item))
|
||||
return False
|
||||
raise
|
||||
except ExtractorError:
|
||||
self.to_screen(
|
||||
'%s: %s URL is invalid, skipping' % (video_id, item))
|
||||
return False
|
||||
|
||||
def http_scheme(self):
|
||||
""" Either "http:" or "https:", depending on the user's preferences """
|
||||
|
118
youtube_dl/extractor/contv.py
Normal file
118
youtube_dl/extractor/contv.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
float_or_none,
|
||||
int_or_none,
|
||||
)
|
||||
|
||||
|
||||
class CONtvIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?contv\.com/details-movie/(?P<id>[^/]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.contv.com/details-movie/CEG10022949/days-of-thrills-&-laughter',
|
||||
'info_dict': {
|
||||
'id': 'CEG10022949',
|
||||
'ext': 'mp4',
|
||||
'title': 'Days Of Thrills & Laughter',
|
||||
'description': 'md5:5d6b3d0b1829bb93eb72898c734802eb',
|
||||
'upload_date': '20180703',
|
||||
'timestamp': 1530634789.61,
|
||||
},
|
||||
'params': {
|
||||
# m3u8 download
|
||||
'skip_download': True,
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www.contv.com/details-movie/CLIP-show_fotld_bts/fight-of-the-living-dead:-behind-the-scenes-bites',
|
||||
'info_dict': {
|
||||
'id': 'CLIP-show_fotld_bts',
|
||||
'title': 'Fight of the Living Dead: Behind the Scenes Bites',
|
||||
},
|
||||
'playlist_mincount': 7,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
details = self._download_json(
|
||||
'http://metax.contv.live.junctiontv.net/metax/2.5/details/' + video_id,
|
||||
video_id, query={'device': 'web'})
|
||||
|
||||
if details.get('type') == 'episodic':
|
||||
seasons = self._download_json(
|
||||
'http://metax.contv.live.junctiontv.net/metax/2.5/seriesfeed/json/' + video_id,
|
||||
video_id)
|
||||
entries = []
|
||||
for season in seasons:
|
||||
for episode in season.get('episodes', []):
|
||||
episode_id = episode.get('id')
|
||||
if not episode_id:
|
||||
continue
|
||||
entries.append(self.url_result(
|
||||
'https://www.contv.com/details-movie/' + episode_id,
|
||||
CONtvIE.ie_key(), episode_id))
|
||||
return self.playlist_result(entries, video_id, details.get('title'))
|
||||
|
||||
m_details = details['details']
|
||||
title = details['title']
|
||||
|
||||
formats = []
|
||||
|
||||
media_hls_url = m_details.get('media_hls_url')
|
||||
if media_hls_url:
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
media_hls_url, video_id, 'mp4',
|
||||
m3u8_id='hls', fatal=False))
|
||||
|
||||
media_mp4_url = m_details.get('media_mp4_url')
|
||||
if media_mp4_url:
|
||||
formats.append({
|
||||
'format_id': 'http',
|
||||
'url': media_mp4_url,
|
||||
})
|
||||
|
||||
self._sort_formats(formats)
|
||||
|
||||
subtitles = {}
|
||||
captions = m_details.get('captions') or {}
|
||||
for caption_url in captions.values():
|
||||
subtitles.setdefault('en', []).append({
|
||||
'url': caption_url
|
||||
})
|
||||
|
||||
thumbnails = []
|
||||
for image in m_details.get('images', []):
|
||||
image_url = image.get('url')
|
||||
if not image_url:
|
||||
continue
|
||||
thumbnails.append({
|
||||
'url': image_url,
|
||||
'width': int_or_none(image.get('width')),
|
||||
'height': int_or_none(image.get('height')),
|
||||
})
|
||||
|
||||
description = None
|
||||
for p in ('large_', 'medium_', 'small_', ''):
|
||||
d = m_details.get(p + 'description')
|
||||
if d:
|
||||
description = d
|
||||
break
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'formats': formats,
|
||||
'thumbnails': thumbnails,
|
||||
'description': description,
|
||||
'timestamp': float_or_none(details.get('metax_added_on'), 1000),
|
||||
'subtitles': subtitles,
|
||||
'duration': float_or_none(m_details.get('duration'), 1000),
|
||||
'view_count': int_or_none(details.get('num_watched')),
|
||||
'like_count': int_or_none(details.get('num_fav')),
|
||||
'categories': details.get('category'),
|
||||
'tags': details.get('tags'),
|
||||
'season_number': int_or_none(details.get('season')),
|
||||
'episode_number': int_or_none(details.get('episode')),
|
||||
'release_year': int_or_none(details.get('pub_year')),
|
||||
}
|
@@ -1,20 +1,17 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..compat import compat_b64decode
|
||||
from ..utils import (
|
||||
int_or_none,
|
||||
qualities,
|
||||
sanitized_Request,
|
||||
)
|
||||
|
||||
|
||||
class DumpertIE(InfoExtractor):
|
||||
_VALID_URL = r'(?P<protocol>https?)://(?:www\.)?dumpert\.nl/(?:mediabase|embed)/(?P<id>[0-9]+/[0-9a-zA-Z]+)'
|
||||
_VALID_URL = r'(?P<protocol>https?)://(?:(?:www|legacy)\.)?dumpert\.nl/(?:mediabase|embed|item)/(?P<id>[0-9]+[/_][0-9a-zA-Z]+)'
|
||||
_TESTS = [{
|
||||
'url': 'http://www.dumpert.nl/mediabase/6646981/951bc60f/',
|
||||
'url': 'https://www.dumpert.nl/item/6646981_951bc60f',
|
||||
'md5': '1b9318d7d5054e7dcb9dc7654f21d643',
|
||||
'info_dict': {
|
||||
'id': '6646981/951bc60f',
|
||||
@@ -24,46 +21,60 @@ class DumpertIE(InfoExtractor):
|
||||
'thumbnail': r're:^https?://.*\.jpg$',
|
||||
}
|
||||
}, {
|
||||
'url': 'http://www.dumpert.nl/embed/6675421/dc440fe7/',
|
||||
'url': 'https://www.dumpert.nl/embed/6675421_dc440fe7',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://legacy.dumpert.nl/mediabase/6646981/951bc60f',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://legacy.dumpert.nl/embed/6675421/dc440fe7',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
protocol = mobj.group('protocol')
|
||||
|
||||
url = '%s://www.dumpert.nl/mediabase/%s' % (protocol, video_id)
|
||||
req = sanitized_Request(url)
|
||||
req.add_header('Cookie', 'nsfw=1; cpc=10')
|
||||
webpage = self._download_webpage(req, video_id)
|
||||
|
||||
files_base64 = self._search_regex(
|
||||
r'data-files="([^"]+)"', webpage, 'data files')
|
||||
|
||||
files = self._parse_json(
|
||||
compat_b64decode(files_base64).decode('utf-8'),
|
||||
video_id)
|
||||
video_id = self._match_id(url).replace('_', '/')
|
||||
item = self._download_json(
|
||||
'http://api-live.dumpert.nl/mobile_api/json/info/' + video_id.replace('/', '_'),
|
||||
video_id)['items'][0]
|
||||
title = item['title']
|
||||
media = next(m for m in item['media'] if m.get('mediatype') == 'VIDEO')
|
||||
|
||||
quality = qualities(['flv', 'mobile', 'tablet', '720p'])
|
||||
|
||||
formats = [{
|
||||
'url': video_url,
|
||||
'format_id': format_id,
|
||||
'quality': quality(format_id),
|
||||
} for format_id, video_url in files.items() if format_id != 'still']
|
||||
formats = []
|
||||
for variant in media.get('variants', []):
|
||||
uri = variant.get('uri')
|
||||
if not uri:
|
||||
continue
|
||||
version = variant.get('version')
|
||||
formats.append({
|
||||
'url': uri,
|
||||
'format_id': version,
|
||||
'quality': quality(version),
|
||||
})
|
||||
self._sort_formats(formats)
|
||||
|
||||
title = self._html_search_meta(
|
||||
'title', webpage) or self._og_search_title(webpage)
|
||||
description = self._html_search_meta(
|
||||
'description', webpage) or self._og_search_description(webpage)
|
||||
thumbnail = files.get('still') or self._og_search_thumbnail(webpage)
|
||||
thumbnails = []
|
||||
stills = item.get('stills') or {}
|
||||
for t in ('thumb', 'still'):
|
||||
for s in ('', '-medium', '-large'):
|
||||
still_id = t + s
|
||||
still_url = stills.get(still_id)
|
||||
if not still_url:
|
||||
continue
|
||||
thumbnails.append({
|
||||
'id': still_id,
|
||||
'url': still_url,
|
||||
})
|
||||
|
||||
stats = item.get('stats') or {}
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'thumbnail': thumbnail,
|
||||
'formats': formats
|
||||
'description': item.get('description'),
|
||||
'thumbnails': thumbnails,
|
||||
'formats': formats,
|
||||
'duration': int_or_none(media.get('duration')),
|
||||
'like_count': int_or_none(stats.get('kudos_total')),
|
||||
'view_count': int_or_none(stats.get('views_total')),
|
||||
}
|
||||
|
@@ -104,6 +104,8 @@ from .bild import BildIE
|
||||
from .bilibili import (
|
||||
BiliBiliIE,
|
||||
BiliBiliBangumiIE,
|
||||
BilibiliAudioIE,
|
||||
BilibiliAudioAlbumIE,
|
||||
)
|
||||
from .biobiochiletv import BioBioChileTVIE
|
||||
from .bitchute import (
|
||||
@@ -229,6 +231,7 @@ from .commonprotocols import (
|
||||
RtmpIE,
|
||||
)
|
||||
from .condenast import CondeNastIE
|
||||
from .contv import CONtvIE
|
||||
from .corus import CorusIE
|
||||
from .cracked import CrackedIE
|
||||
from .crackle import CrackleIE
|
||||
@@ -642,7 +645,7 @@ from .minhateca import MinhatecaIE
|
||||
from .ministrygrid import MinistryGridIE
|
||||
from .minoto import MinotoIE
|
||||
from .miomio import MioMioIE
|
||||
from .mit import TechTVMITIE, MITIE, OCWMITIE
|
||||
from .mit import TechTVMITIE, OCWMITIE
|
||||
from .mitele import MiTeleIE
|
||||
from .mixcloud import (
|
||||
MixcloudIE,
|
||||
@@ -891,7 +894,6 @@ from .puhutv import (
|
||||
PuhuTVSerieIE,
|
||||
)
|
||||
from .presstv import PressTVIE
|
||||
from .promptfile import PromptFileIE
|
||||
from .prosiebensat1 import ProSiebenSat1IE
|
||||
from .puls4 import Puls4IE
|
||||
from .pyvideo import PyvideoIE
|
||||
@@ -994,7 +996,6 @@ from .scrippsnetworks import ScrippsNetworksWatchIE
|
||||
from .seeker import SeekerIE
|
||||
from .senateisvp import SenateISVPIE
|
||||
from .sendtonews import SendtoNewsIE
|
||||
from .servingsys import ServingSysIE
|
||||
from .servus import ServusIE
|
||||
from .sevenplus import SevenPlusIE
|
||||
from .sexu import SexuIE
|
||||
@@ -1127,6 +1128,7 @@ from .telegraaf import TelegraafIE
|
||||
from .telemb import TeleMBIE
|
||||
from .telequebec import (
|
||||
TeleQuebecIE,
|
||||
TeleQuebecSquatIE,
|
||||
TeleQuebecEmissionIE,
|
||||
TeleQuebecLiveIE,
|
||||
)
|
||||
@@ -1280,7 +1282,6 @@ from .varzesh3 import Varzesh3IE
|
||||
from .vbox7 import Vbox7IE
|
||||
from .veehd import VeeHDIE
|
||||
from .veoh import VeohIE
|
||||
from .vessel import VesselIE
|
||||
from .vesti import VestiIE
|
||||
from .vevo import (
|
||||
VevoIE,
|
||||
@@ -1322,7 +1323,6 @@ from .viewlift import (
|
||||
ViewLiftIE,
|
||||
ViewLiftEmbedIE,
|
||||
)
|
||||
from .viewster import ViewsterIE
|
||||
from .viidea import ViideaIE
|
||||
from .vimeo import (
|
||||
VimeoIE,
|
||||
@@ -1411,7 +1411,6 @@ from .weibo import (
|
||||
WeiboMobileIE
|
||||
)
|
||||
from .weiqitv import WeiqiTVIE
|
||||
from .wimp import WimpIE
|
||||
from .wistia import WistiaIE
|
||||
from .worldstarhiphop import WorldStarHipHopIE
|
||||
from .wsj import (
|
||||
|
@@ -405,6 +405,11 @@ class FacebookIE(InfoExtractor):
|
||||
if not formats:
|
||||
raise ExtractorError('Cannot find video formats')
|
||||
|
||||
# Downloads with browser's User-Agent are rate limited. Working around
|
||||
# with non-browser User-Agent.
|
||||
for f in formats:
|
||||
f.setdefault('http_headers', {})['User-Agent'] = 'facebookexternalhit/1.1'
|
||||
|
||||
self._sort_formats(formats)
|
||||
|
||||
video_title = self._html_search_regex(
|
||||
|
@@ -77,7 +77,6 @@ from .instagram import InstagramIE
|
||||
from .liveleak import LiveLeakIE
|
||||
from .threeqsdn import ThreeQSDNIE
|
||||
from .theplatform import ThePlatformIE
|
||||
from .vessel import VesselIE
|
||||
from .kaltura import KalturaIE
|
||||
from .eagleplatform import EaglePlatformIE
|
||||
from .facebook import FacebookIE
|
||||
@@ -2491,11 +2490,6 @@ class GenericIE(InfoExtractor):
|
||||
if tp_urls:
|
||||
return self.playlist_from_matches(tp_urls, video_id, video_title, ie='ThePlatform')
|
||||
|
||||
# Look for Vessel embeds
|
||||
vessel_urls = VesselIE._extract_urls(webpage)
|
||||
if vessel_urls:
|
||||
return self.playlist_from_matches(vessel_urls, video_id, video_title, ie=VesselIE.ie_key())
|
||||
|
||||
# Look for embedded rtl.nl player
|
||||
matches = re.findall(
|
||||
r'<iframe[^>]+?src="((?:https?:)?//(?:(?:www|static)\.)?rtl\.nl/(?:system/videoplayer/[^"]+(?:video_)?)?embed[^"]+)"',
|
||||
@@ -2968,10 +2962,14 @@ class GenericIE(InfoExtractor):
|
||||
|
||||
# Look for Mangomolo embeds
|
||||
mobj = re.search(
|
||||
r'''(?x)<iframe[^>]+src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?admin\.mangomolo\.com/analytics/index\.php/customers/embed/
|
||||
r'''(?x)<iframe[^>]+src=(["\'])(?P<url>(?:https?:)?//
|
||||
(?:
|
||||
admin\.mangomolo\.com/analytics/index\.php/customers/embed|
|
||||
player\.mangomolo\.com/v1
|
||||
)/
|
||||
(?:
|
||||
video\?.*?\bid=(?P<video_id>\d+)|
|
||||
index\?.*?\bchannelid=(?P<channel_id>(?:[A-Za-z0-9+/=]|%2B|%2F|%3D)+)
|
||||
(?:index|live)\?.*?\bchannelid=(?P<channel_id>(?:[A-Za-z0-9+/=]|%2B|%2F|%3D)+)
|
||||
).+?)\1''', webpage)
|
||||
if mobj is not None:
|
||||
info = {
|
||||
|
@@ -11,7 +11,7 @@ from ..utils import (
|
||||
|
||||
|
||||
class GfycatIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?gfycat\.com/(?:ru/|ifr/|gifs/detail/)?(?P<id>[^-/?#]+)'
|
||||
_VALID_URL = r'https?://(?:(?:www|giant|thumbs)\.)?gfycat\.com/(?:ru/|ifr/|gifs/detail/)?(?P<id>[^-/?#\.]+)'
|
||||
_TESTS = [{
|
||||
'url': 'http://gfycat.com/DeadlyDecisiveGermanpinscher',
|
||||
'info_dict': {
|
||||
@@ -53,6 +53,12 @@ class GfycatIE(InfoExtractor):
|
||||
}, {
|
||||
'url': 'https://gfycat.com/acceptablehappygoluckyharborporpoise-baseball',
|
||||
'only_matching': True
|
||||
}, {
|
||||
'url': 'https://thumbs.gfycat.com/acceptablehappygoluckyharborporpoise-size_restricted.gif',
|
||||
'only_matching': True
|
||||
}, {
|
||||
'url': 'https://giant.gfycat.com/acceptablehappygoluckyharborporpoise.mp4',
|
||||
'only_matching': True
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
@@ -96,21 +96,31 @@ class GloboIE(InfoExtractor):
|
||||
video = self._download_json(
|
||||
'http://api.globovideos.com/videos/%s/playlist' % video_id,
|
||||
video_id)['videos'][0]
|
||||
if video.get('encrypted') is True:
|
||||
raise ExtractorError('This video is DRM protected.', expected=True)
|
||||
|
||||
title = video['title']
|
||||
|
||||
formats = []
|
||||
subtitles = {}
|
||||
for resource in video['resources']:
|
||||
resource_id = resource.get('_id')
|
||||
resource_url = resource.get('url')
|
||||
if not resource_id or not resource_url:
|
||||
resource_type = resource.get('type')
|
||||
if not resource_url or (resource_type == 'media' and not resource_id) or resource_type not in ('subtitle', 'media'):
|
||||
continue
|
||||
|
||||
if resource_type == 'subtitle':
|
||||
subtitles.setdefault(resource.get('language') or 'por', []).append({
|
||||
'url': resource_url,
|
||||
})
|
||||
continue
|
||||
|
||||
security = self._download_json(
|
||||
'http://security.video.globo.com/videos/%s/hash' % video_id,
|
||||
video_id, 'Downloading security hash for %s' % resource_id, query={
|
||||
'player': 'flash',
|
||||
'version': '17.0.0.132',
|
||||
'player': 'desktop',
|
||||
'version': '5.19.1',
|
||||
'resource_id': resource_id,
|
||||
})
|
||||
|
||||
@@ -122,19 +132,18 @@ class GloboIE(InfoExtractor):
|
||||
'%s returned error: %s' % (self.IE_NAME, message), expected=True)
|
||||
continue
|
||||
|
||||
hash_code = security_hash[:2]
|
||||
received_time = security_hash[2:12]
|
||||
received_random = security_hash[12:22]
|
||||
received_md5 = security_hash[22:]
|
||||
assert security_hash[:2] in ('04', '14')
|
||||
received_time = security_hash[3:13]
|
||||
received_md5 = security_hash[24:]
|
||||
|
||||
sign_time = compat_str(int(received_time) + 86400)
|
||||
padding = '%010d' % random.randint(1, 10000000000)
|
||||
|
||||
md5_data = (received_md5 + sign_time + padding + '0xFF01DD').encode()
|
||||
md5_data = (received_md5 + sign_time + padding + '0xAC10FD').encode()
|
||||
signed_md5 = base64.urlsafe_b64encode(hashlib.md5(md5_data).digest()).decode().strip('=')
|
||||
signed_hash = hash_code + received_time + received_random + sign_time + padding + signed_md5
|
||||
signed_hash = security_hash[:23] + sign_time + padding + signed_md5
|
||||
|
||||
signed_url = '%s?h=%s&k=%s' % (resource_url, signed_hash, 'flash')
|
||||
signed_url = '%s?h=%s&k=html5&a=%s&u=%s' % (resource_url, signed_hash, 'F' if video.get('subscriber_only') else 'A', security.get('user') or '')
|
||||
if resource_id.endswith('m3u8') or resource_url.endswith('.m3u8'):
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
signed_url, resource_id, 'mp4', entry_protocol='m3u8_native',
|
||||
@@ -164,7 +173,8 @@ class GloboIE(InfoExtractor):
|
||||
'duration': duration,
|
||||
'uploader': uploader,
|
||||
'uploader_id': uploader_id,
|
||||
'formats': formats
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
}
|
||||
|
||||
|
||||
|
@@ -105,8 +105,7 @@ class HeiseIE(InfoExtractor):
|
||||
webpage, default=None) or self._html_search_meta(
|
||||
'description', webpage)
|
||||
|
||||
kaltura_url = KalturaIE._extract_url(webpage)
|
||||
if kaltura_url:
|
||||
def _make_kaltura_result(kaltura_url):
|
||||
return {
|
||||
'_type': 'url_transparent',
|
||||
'url': smuggle_url(kaltura_url, {'source_url': url}),
|
||||
@@ -115,6 +114,16 @@ class HeiseIE(InfoExtractor):
|
||||
'description': description,
|
||||
}
|
||||
|
||||
kaltura_url = KalturaIE._extract_url(webpage)
|
||||
if kaltura_url:
|
||||
return _make_kaltura_result(kaltura_url)
|
||||
|
||||
kaltura_id = self._search_regex(
|
||||
r'entry-id=(["\'])(?P<id>(?:(?!\1).)+)\1', webpage, 'kaltura id',
|
||||
default=None, group='id')
|
||||
if kaltura_id:
|
||||
return _make_kaltura_result('kaltura:2238431:%s' % kaltura_id)
|
||||
|
||||
yt_urls = YoutubeIE._extract_urls(webpage)
|
||||
if yt_urls:
|
||||
return self.playlist_from_matches(
|
||||
|
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import re
|
||||
import time
|
||||
import uuid
|
||||
|
||||
@@ -126,6 +127,8 @@ class HotStarIE(HotStarBaseIE):
|
||||
format_url = url_or_none(playback_set.get('playbackUrl'))
|
||||
if not format_url:
|
||||
continue
|
||||
format_url = re.sub(
|
||||
r'(?<=//staragvod)(\d)', r'web\1', format_url)
|
||||
tags = str_or_none(playback_set.get('tagsCombination')) or ''
|
||||
if tags and 'encryption:plain' not in tags:
|
||||
continue
|
||||
@@ -133,7 +136,8 @@ class HotStarIE(HotStarBaseIE):
|
||||
try:
|
||||
if 'package:hls' in tags or ext == 'm3u8':
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
format_url, video_id, 'mp4', m3u8_id='hls'))
|
||||
format_url, video_id, 'mp4',
|
||||
entry_protocol='m3u8_native', m3u8_id='hls'))
|
||||
elif 'package:dash' in tags or ext == 'mpd':
|
||||
formats.extend(self._extract_mpd_formats(
|
||||
format_url, video_id, mpd_id='dash'))
|
||||
|
@@ -22,7 +22,7 @@ from ..utils import (
|
||||
|
||||
|
||||
class InstagramIE(InfoExtractor):
|
||||
_VALID_URL = r'(?P<url>https?://(?:www\.)?instagram\.com/p/(?P<id>[^/?#&]+))'
|
||||
_VALID_URL = r'(?P<url>https?://(?:www\.)?instagram\.com/(?:p|tv)/(?P<id>[^/?#&]+))'
|
||||
_TESTS = [{
|
||||
'url': 'https://instagram.com/p/aye83DjauH/?foo=bar#abc',
|
||||
'md5': '0d2da106a9d2631273e192b372806516',
|
||||
@@ -92,6 +92,9 @@ class InstagramIE(InfoExtractor):
|
||||
}, {
|
||||
'url': 'http://instagram.com/p/9o6LshA7zy/embed/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.instagram.com/tv/aye83DjauH/',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
@staticmethod
|
||||
|
@@ -7,7 +7,7 @@ from .common import InfoExtractor
|
||||
|
||||
|
||||
class JWPlatformIE(InfoExtractor):
|
||||
_VALID_URL = r'(?:https?://(?:content\.jwplatform|cdn\.jwplayer)\.com/(?:(?:feed|player|thumb|preview|video)s|jw6|v2/media)/|jwplatform:)(?P<id>[a-zA-Z0-9]{8})'
|
||||
_VALID_URL = r'(?:https?://(?:content\.jwplatform|cdn\.jwplayer)\.com/(?:(?:feed|player|thumb|preview)s|jw6|v2/media)/|jwplatform:)(?P<id>[a-zA-Z0-9]{8})'
|
||||
_TESTS = [{
|
||||
'url': 'http://content.jwplatform.com/players/nPripu9l-ALJ3XQCI.js',
|
||||
'md5': 'fa8899fa601eb7c83a64e9d568bdf325',
|
||||
|
@@ -151,14 +151,15 @@ class KalturaIE(InfoExtractor):
|
||||
if mobj:
|
||||
embed_info = mobj.groupdict()
|
||||
for k, v in embed_info.items():
|
||||
embed_info[k] = v.strip()
|
||||
if v:
|
||||
embed_info[k] = v.strip()
|
||||
url = 'kaltura:%(partner_id)s:%(id)s' % embed_info
|
||||
escaped_pid = re.escape(embed_info['partner_id'])
|
||||
service_url = re.search(
|
||||
r'<script[^>]+src=["\']((?:https?:)?//.+?)/p/%s/sp/%s00/embedIframeJs' % (escaped_pid, escaped_pid),
|
||||
service_mobj = re.search(
|
||||
r'<script[^>]+src=(["\'])(?P<id>(?:https?:)?//(?:(?!\1).)+)/p/%s/sp/%s00/embedIframeJs' % (escaped_pid, escaped_pid),
|
||||
webpage)
|
||||
if service_url:
|
||||
url = smuggle_url(url, {'service_url': service_url.group(1)})
|
||||
if service_mobj:
|
||||
url = smuggle_url(url, {'service_url': service_mobj.group('id')})
|
||||
return url
|
||||
|
||||
def _kaltura_api_call(self, video_id, actions, service_url=None, *args, **kwargs):
|
||||
|
@@ -10,18 +10,21 @@ from ..utils import int_or_none
|
||||
|
||||
|
||||
class MangomoloBaseIE(InfoExtractor):
|
||||
_BASE_REGEX = r'https?://(?:admin\.mangomolo\.com/analytics/index\.php/customers/embed/|player\.mangomolo\.com/v1/)'
|
||||
|
||||
def _get_real_id(self, page_id):
|
||||
return page_id
|
||||
|
||||
def _real_extract(self, url):
|
||||
page_id = self._get_real_id(self._match_id(url))
|
||||
webpage = self._download_webpage(url, page_id)
|
||||
webpage = self._download_webpage(
|
||||
'https://player.mangomolo.com/v1/%s?%s' % (self._TYPE, url.split('?')[1]), page_id)
|
||||
hidden_inputs = self._hidden_inputs(webpage)
|
||||
m3u8_entry_protocol = 'm3u8' if self._IS_LIVE else 'm3u8_native'
|
||||
|
||||
format_url = self._html_search_regex(
|
||||
[
|
||||
r'file\s*:\s*"(https?://[^"]+?/playlist\.m3u8)',
|
||||
r'(?:file|src)\s*:\s*"(https?://[^"]+?/playlist\.m3u8)',
|
||||
r'<a[^>]+href="(rtsp://[^"]+)"'
|
||||
], webpage, 'format url')
|
||||
formats = self._extract_wowza_formats(
|
||||
@@ -39,14 +42,16 @@ class MangomoloBaseIE(InfoExtractor):
|
||||
|
||||
|
||||
class MangomoloVideoIE(MangomoloBaseIE):
|
||||
IE_NAME = 'mangomolo:video'
|
||||
_VALID_URL = r'https?://admin\.mangomolo\.com/analytics/index\.php/customers/embed/video\?.*?\bid=(?P<id>\d+)'
|
||||
_TYPE = 'video'
|
||||
IE_NAME = 'mangomolo:' + _TYPE
|
||||
_VALID_URL = MangomoloBaseIE._BASE_REGEX + r'video\?.*?\bid=(?P<id>\d+)'
|
||||
_IS_LIVE = False
|
||||
|
||||
|
||||
class MangomoloLiveIE(MangomoloBaseIE):
|
||||
IE_NAME = 'mangomolo:live'
|
||||
_VALID_URL = r'https?://admin\.mangomolo\.com/analytics/index\.php/customers/embed/index\?.*?\bchannelid=(?P<id>(?:[A-Za-z0-9+/=]|%2B|%2F|%3D)+)'
|
||||
_TYPE = 'live'
|
||||
IE_NAME = 'mangomolo:' + _TYPE
|
||||
_VALID_URL = MangomoloBaseIE._BASE_REGEX + r'(live|index)\?.*?\bchannelid=(?P<id>(?:[A-Za-z0-9+/=]|%2B|%2F|%3D)+)'
|
||||
_IS_LIVE = True
|
||||
|
||||
def _get_real_id(self, page_id):
|
||||
|
@@ -65,30 +65,6 @@ class TechTVMITIE(InfoExtractor):
|
||||
}
|
||||
|
||||
|
||||
class MITIE(TechTVMITIE):
|
||||
IE_NAME = 'video.mit.edu'
|
||||
_VALID_URL = r'https?://video\.mit\.edu/watch/(?P<title>[^/]+)'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://video.mit.edu/watch/the-government-is-profiling-you-13222/',
|
||||
'md5': '7db01d5ccc1895fc5010e9c9e13648da',
|
||||
'info_dict': {
|
||||
'id': '21783',
|
||||
'ext': 'mp4',
|
||||
'title': 'The Government is Profiling You',
|
||||
'description': 'md5:ad5795fe1e1623b73620dbfd47df9afd',
|
||||
},
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
page_title = mobj.group('title')
|
||||
webpage = self._download_webpage(url, page_title)
|
||||
embed_url = self._search_regex(
|
||||
r'<iframe .*?src="(.+?)"', webpage, 'embed url')
|
||||
return self.url_result(embed_url)
|
||||
|
||||
|
||||
class OCWMITIE(InfoExtractor):
|
||||
IE_NAME = 'ocw.mit.edu'
|
||||
_VALID_URL = r'^https?://ocw\.mit\.edu/courses/(?P<topic>[a-z0-9\-]+)'
|
||||
|
@@ -164,7 +164,7 @@ class MixcloudIE(InfoExtractor):
|
||||
def decrypt_url(f_url):
|
||||
for k in (key, 'IFYOUWANTTHEARTISTSTOGETPAIDDONOTDOWNLOADFROMMIXCLOUD'):
|
||||
decrypted_url = self._decrypt_xor_cipher(k, f_url)
|
||||
if re.search(r'^https?://[0-9a-z.]+/[0-9A-Za-z/.?=&_-]+$', decrypted_url):
|
||||
if re.search(r'^https?://[0-9A-Za-z.]+/[0-9A-Za-z/.?=&_-]+$', decrypted_url):
|
||||
return decrypted_url
|
||||
|
||||
for url_key in ('url', 'hlsUrl', 'dashUrl'):
|
||||
|
@@ -10,7 +10,6 @@ from .adobepass import AdobePassIE
|
||||
from ..compat import compat_urllib_parse_unquote
|
||||
from ..utils import (
|
||||
smuggle_url,
|
||||
try_get,
|
||||
update_url_query,
|
||||
int_or_none,
|
||||
)
|
||||
@@ -85,27 +84,41 @@ class NBCIE(AdobePassIE):
|
||||
permalink, video_id = re.match(self._VALID_URL, url).groups()
|
||||
permalink = 'http' + compat_urllib_parse_unquote(permalink)
|
||||
response = self._download_json(
|
||||
'https://api.nbc.com/v3/videos', video_id, query={
|
||||
'filter[permalink]': permalink,
|
||||
'fields[videos]': 'description,entitlement,episodeNumber,guid,keywords,seasonNumber,title,vChipRating',
|
||||
'fields[shows]': 'shortTitle',
|
||||
'include': 'show.shortTitle',
|
||||
'https://friendship.nbc.co/v2/graphql', video_id, query={
|
||||
'query': '''{
|
||||
page(name: "%s", platform: web, type: VIDEO, userId: "0") {
|
||||
data {
|
||||
... on VideoPageData {
|
||||
description
|
||||
episodeNumber
|
||||
keywords
|
||||
locked
|
||||
mpxAccountId
|
||||
mpxGuid
|
||||
rating
|
||||
seasonNumber
|
||||
secondaryTitle
|
||||
seriesShortTitle
|
||||
}
|
||||
}
|
||||
}
|
||||
}''' % permalink,
|
||||
})
|
||||
video_data = response['data'][0]['attributes']
|
||||
video_data = response['data']['page']['data']
|
||||
query = {
|
||||
'mbr': 'true',
|
||||
'manifest': 'm3u',
|
||||
}
|
||||
video_id = video_data['guid']
|
||||
title = video_data['title']
|
||||
if video_data.get('entitlement') == 'auth':
|
||||
video_id = video_data['mpxGuid']
|
||||
title = video_data['secondaryTitle']
|
||||
if video_data.get('locked'):
|
||||
resource = self._get_mvpd_resource(
|
||||
'nbcentertainment', title, video_id,
|
||||
video_data.get('vChipRating'))
|
||||
video_data.get('rating'))
|
||||
query['auth'] = self._extract_mvpd_auth(
|
||||
url, video_id, 'nbcentertainment', resource)
|
||||
theplatform_url = smuggle_url(update_url_query(
|
||||
'http://link.theplatform.com/s/NnzsPC/media/guid/2410887629/' + video_id,
|
||||
'http://link.theplatform.com/s/NnzsPC/media/guid/%s/%s' % (video_data.get('mpxAccountId') or '2410887629', video_id),
|
||||
query), {'force_smil_url': True})
|
||||
return {
|
||||
'_type': 'url_transparent',
|
||||
@@ -117,7 +130,7 @@ class NBCIE(AdobePassIE):
|
||||
'season_number': int_or_none(video_data.get('seasonNumber')),
|
||||
'episode_number': int_or_none(video_data.get('episodeNumber')),
|
||||
'episode': title,
|
||||
'series': try_get(response, lambda x: x['included'][0]['attributes']['shortTitle']),
|
||||
'series': video_data.get('seriesShortTitle'),
|
||||
'ie_key': 'ThePlatform',
|
||||
}
|
||||
|
||||
|
@@ -295,13 +295,23 @@ class NexxIE(InfoExtractor):
|
||||
|
||||
video = None
|
||||
|
||||
def find_video(result):
|
||||
if isinstance(result, dict):
|
||||
return result
|
||||
elif isinstance(result, list):
|
||||
vid = int(video_id)
|
||||
for v in result:
|
||||
if try_get(v, lambda x: x['general']['ID'], int) == vid:
|
||||
return v
|
||||
return None
|
||||
|
||||
response = self._download_json(
|
||||
'https://arc.nexx.cloud/api/video/%s.json' % video_id,
|
||||
video_id, fatal=False)
|
||||
if response and isinstance(response, dict):
|
||||
result = response.get('result')
|
||||
if result and isinstance(result, dict):
|
||||
video = result
|
||||
if result:
|
||||
video = find_video(result)
|
||||
|
||||
# not all videos work via arc, e.g. nexx:741:1269984
|
||||
if not video:
|
||||
@@ -348,7 +358,7 @@ class NexxIE(InfoExtractor):
|
||||
request_token = hashlib.md5(
|
||||
''.join((op, domain_id, secret)).encode('utf-8')).hexdigest()
|
||||
|
||||
video = self._call_api(
|
||||
result = self._call_api(
|
||||
domain_id, 'videos/%s/%s' % (op, video_id), video_id, data={
|
||||
'additionalfields': 'language,channel,actors,studio,licenseby,slug,subtitle,teaser,description',
|
||||
'addInteractionOptions': '1',
|
||||
@@ -363,6 +373,7 @@ class NexxIE(InfoExtractor):
|
||||
'X-Request-CID': cid,
|
||||
'X-Request-Token': request_token,
|
||||
})
|
||||
video = find_video(result)
|
||||
|
||||
general = video['general']
|
||||
title = general['title']
|
||||
|
@@ -10,6 +10,18 @@ class NhkVodIE(InfoExtractor):
|
||||
# Content available only for a limited period of time. Visit
|
||||
# https://www3.nhk.or.jp/nhkworld/en/ondemand/ for working samples.
|
||||
_TESTS = [{
|
||||
# clip
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/video/9999011/',
|
||||
'md5': '256a1be14f48d960a7e61e2532d95ec3',
|
||||
'info_dict': {
|
||||
'id': 'a95j5iza',
|
||||
'ext': 'mp4',
|
||||
'title': "Dining with the Chef - Chef Saito's Family recipe: MENCHI-KATSU",
|
||||
'description': 'md5:5aee4a9f9d81c26281862382103b0ea5',
|
||||
'timestamp': 1565965194,
|
||||
'upload_date': '20190816',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/video/2015173/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
@@ -19,7 +31,7 @@ class NhkVodIE(InfoExtractor):
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/fr/ondemand/audio/plugin-20190404-1/',
|
||||
'only_matching': True,
|
||||
}]
|
||||
_API_URL_TEMPLATE = 'https://api.nhk.or.jp/nhkworld/%sodesdlist/v7/episode/%s/%s/all%s.json'
|
||||
_API_URL_TEMPLATE = 'https://api.nhk.or.jp/nhkworld/%sod%slist/v7/episode/%s/%s/all%s.json'
|
||||
|
||||
def _real_extract(self, url):
|
||||
lang, m_type, episode_id = re.match(self._VALID_URL, url).groups()
|
||||
@@ -28,7 +40,10 @@ class NhkVodIE(InfoExtractor):
|
||||
|
||||
is_video = m_type == 'video'
|
||||
episode = self._download_json(
|
||||
self._API_URL_TEMPLATE % ('v' if is_video else 'r', episode_id, lang, '/all' if is_video else ''),
|
||||
self._API_URL_TEMPLATE % (
|
||||
'v' if is_video else 'r',
|
||||
'clip' if episode_id[:4] == '9999' else 'esd',
|
||||
episode_id, lang, '/all' if is_video else ''),
|
||||
episode_id, query={'apikey': 'EJfK8jdS57GqlupFgAfAAwr573q01y6k'})['data']['episodes'][0]
|
||||
title = episode.get('sub_title_clean') or episode['sub_title']
|
||||
|
||||
@@ -60,8 +75,8 @@ class NhkVodIE(InfoExtractor):
|
||||
if is_video:
|
||||
info.update({
|
||||
'_type': 'url_transparent',
|
||||
'ie_key': 'Ooyala',
|
||||
'url': 'ooyala:' + episode['vod_id'],
|
||||
'ie_key': 'Piksel',
|
||||
'url': 'https://player.piksel.com/v/refid/nhkworld/prefid/' + episode['vod_id'],
|
||||
})
|
||||
else:
|
||||
audio = episode['audio']
|
||||
|
@@ -45,7 +45,11 @@ class NineNowIE(InfoExtractor):
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
page_data = self._parse_json(self._search_regex(
|
||||
r'window\.__data\s*=\s*({.*?});', webpage,
|
||||
'page data'), display_id)
|
||||
'page data', default='{}'), display_id, fatal=False)
|
||||
if not page_data:
|
||||
page_data = self._parse_json(self._parse_json(self._search_regex(
|
||||
r'window\.__data\s*=\s*JSON\.parse\s*\(\s*(".+?")\s*\)\s*;',
|
||||
webpage, 'page data'), display_id), display_id)
|
||||
|
||||
for kind in ('episode', 'clip'):
|
||||
current_key = page_data.get(kind, {}).get(
|
||||
|
@@ -25,9 +25,14 @@ class NonkTubeIE(NuevoBaseIE):
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
info = self._extract_nuevo(
|
||||
'https://www.nonktube.com/media/nuevo/econfig.php?key=%s'
|
||||
% video_id, video_id)
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
info['age_limit'] = 18
|
||||
title = self._og_search_title(webpage)
|
||||
info = self._parse_html5_media_entries(url, webpage, video_id)[0]
|
||||
|
||||
info.update({
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'age_limit': 18,
|
||||
})
|
||||
return info
|
||||
|
@@ -406,7 +406,7 @@ class NRKTVSerieBaseIE(InfoExtractor):
|
||||
def _extract_series(self, webpage, display_id, fatal=True):
|
||||
config = self._parse_json(
|
||||
self._search_regex(
|
||||
(r'INITIAL_DATA_*\s*=\s*({.+?})\s*;',
|
||||
(r'INITIAL_DATA(?:_V\d)?_*\s*=\s*({.+?})\s*;',
|
||||
r'({.+?})\s*,\s*"[^"]+"\s*\)\s*</script>'),
|
||||
webpage, 'config', default='{}' if not fatal else NO_DEFAULT),
|
||||
display_id, fatal=False)
|
||||
|
@@ -246,7 +246,7 @@ class OpenloadIE(InfoExtractor):
|
||||
_DOMAINS = r'''
|
||||
(?:
|
||||
openload\.(?:co|io|link|pw)|
|
||||
oload\.(?:tv|best|biz|stream|site|xyz|win|download|cloud|cc|icu|fun|club|info|press|pw|life|live|space|services|website|vip)|
|
||||
oload\.(?:tv|best|biz|stream|site|xyz|win|download|cloud|cc|icu|fun|club|info|online|monster|press|pw|life|live|space|services|website|vip)|
|
||||
oladblock\.(?:services|xyz|me)|openloed\.co
|
||||
)
|
||||
'''
|
||||
@@ -362,6 +362,12 @@ class OpenloadIE(InfoExtractor):
|
||||
}, {
|
||||
'url': 'https://oload.services/embed/bs1NWj1dCag/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://oload.online/f/W8o2UfN1vNY/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://oload.monster/f/W8o2UfN1vNY/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://oload.press/embed/drTBl1aOTvk/',
|
||||
'only_matching': True,
|
||||
|
@@ -86,12 +86,13 @@ class ORFTVthekIE(InfoExtractor):
|
||||
if value:
|
||||
format_id_list.append(value)
|
||||
format_id = '-'.join(format_id_list)
|
||||
if determine_ext(fd['src']) == 'm3u8':
|
||||
ext = determine_ext(src)
|
||||
if ext == 'm3u8':
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
fd['src'], video_id, 'mp4', m3u8_id=format_id))
|
||||
elif determine_ext(fd['src']) == 'f4m':
|
||||
src, video_id, 'mp4', m3u8_id=format_id, fatal=False))
|
||||
elif ext == 'f4m':
|
||||
formats.extend(self._extract_f4m_formats(
|
||||
fd['src'], video_id, f4m_id=format_id))
|
||||
src, video_id, f4m_id=format_id, fatal=False))
|
||||
else:
|
||||
formats.append({
|
||||
'format_id': format_id,
|
||||
|
@@ -18,81 +18,385 @@ from ..utils import (
|
||||
class PeerTubeIE(InfoExtractor):
|
||||
_INSTANCES_RE = r'''(?:
|
||||
# Taken from https://instances.joinpeertube.org/instances
|
||||
peertube\.rainbowswingers\.net|
|
||||
tube\.stanisic\.nl|
|
||||
peer\.suiri\.us|
|
||||
medias\.libox\.fr|
|
||||
videomensoif\.ynh\.fr|
|
||||
peertube\.travelpandas\.eu|
|
||||
peertube\.rachetjay\.fr|
|
||||
peertube\.montecsys\.fr|
|
||||
tube\.eskuero\.me|
|
||||
peer\.tube|
|
||||
peertube\.umeahackerspace\.se|
|
||||
tube\.nx-pod\.de|
|
||||
video\.monsieurbidouille\.fr|
|
||||
tube\.openalgeria\.org|
|
||||
peertube\.pointsecu\.fr|
|
||||
vid\.lelux\.fi|
|
||||
video\.anormallostpod\.ovh|
|
||||
tube\.crapaud-fou\.org|
|
||||
peertube\.stemy\.me|
|
||||
lostpod\.space|
|
||||
exode\.me|
|
||||
peertube\.snargol\.com|
|
||||
vis\.ion\.ovh|
|
||||
videosdulib\.re|
|
||||
v\.mbius\.io|
|
||||
videos\.judrey\.eu|
|
||||
peertube\.osureplayviewer\.xyz|
|
||||
peertube\.mathieufamily\.ovh|
|
||||
www\.videos-libr\.es|
|
||||
fightforinfo\.com|
|
||||
peertube\.fediverse\.ru|
|
||||
peertube\.oiseauroch\.fr|
|
||||
video\.nesven\.eu|
|
||||
v\.bearvideo\.win|
|
||||
video\.qoto\.org|
|
||||
justporn\.cc|
|
||||
video\.vny\.fr|
|
||||
peervideo\.club|
|
||||
tube\.taker\.fr|
|
||||
peertube\.chantierlibre\.org|
|
||||
tube\.ipfixe\.info|
|
||||
tube\.kicou\.info|
|
||||
tube\.dodsorf\.as|
|
||||
videobit\.cc|
|
||||
video\.yukari\.moe|
|
||||
videos\.elbinario\.net|
|
||||
hkvideo\.live|
|
||||
pt\.tux\.tf|
|
||||
www\.hkvideo\.live|
|
||||
FIGHTFORINFO\.com|
|
||||
pt\.765racing\.com|
|
||||
peertube\.gnumeria\.eu\.org|
|
||||
nordenmedia\.com|
|
||||
peertube\.co\.uk|
|
||||
tube\.darfweb\.eu|
|
||||
tube\.kalah-france\.org|
|
||||
0ch\.in|
|
||||
vod\.mochi\.academy|
|
||||
film\.node9\.org|
|
||||
peertube\.hatthieves\.es|
|
||||
video\.fitchfamily\.org|
|
||||
peertube\.ddns\.net|
|
||||
video\.ifuncle\.kr|
|
||||
video\.fdlibre\.eu|
|
||||
tube\.22decembre\.eu|
|
||||
peertube\.harmoniescreatives\.com|
|
||||
tube\.fabrigli\.fr|
|
||||
video\.thedwyers\.co|
|
||||
video\.bruitbruit\.com|
|
||||
peertube\.foxfam\.club|
|
||||
peer\.philoxweb\.be|
|
||||
videos\.bugs\.social|
|
||||
peertube\.malbert\.xyz|
|
||||
peertube\.bilange\.ca|
|
||||
libretube\.net|
|
||||
diytelevision\.com|
|
||||
peertube\.fedilab\.app|
|
||||
libre\.video|
|
||||
video\.mstddntfdn\.online|
|
||||
us\.tv|
|
||||
peertube\.sl-network\.fr|
|
||||
peertube\.dynlinux\.io|
|
||||
peertube\.david\.durieux\.family|
|
||||
peertube\.linuxrocks\.online|
|
||||
peerwatch\.xyz|
|
||||
v\.kretschmann\.social|
|
||||
tube\.otter\.sh|
|
||||
yt\.is\.nota\.live|
|
||||
tube\.dragonpsi\.xyz|
|
||||
peertube\.boneheadmedia\.com|
|
||||
videos\.funkwhale\.audio|
|
||||
watch\.44con\.com|
|
||||
peertube\.gcaillaut\.fr|
|
||||
peertube\.icu|
|
||||
pony\.tube|
|
||||
spacepub\.space|
|
||||
tube\.stbr\.io|
|
||||
v\.mom-gay\.faith|
|
||||
tube\.port0\.xyz|
|
||||
peertube\.simounet\.net|
|
||||
play\.jergefelt\.se|
|
||||
peertube\.zeteo\.me|
|
||||
tube\.danq\.me|
|
||||
peertube\.kerenon\.com|
|
||||
tube\.fab-l3\.org|
|
||||
tube\.calculate\.social|
|
||||
peertube\.mckillop\.org|
|
||||
tube\.netzspielplatz\.de|
|
||||
vod\.ksite\.de|
|
||||
peertube\.laas\.fr|
|
||||
tube\.govital\.net|
|
||||
peertube\.stephenson\.cc|
|
||||
bistule\.nohost\.me|
|
||||
peertube\.kajalinifi\.de|
|
||||
video\.ploud\.jp|
|
||||
video\.omniatv\.com|
|
||||
peertube\.ffs2play\.fr|
|
||||
peertube\.leboulaire\.ovh|
|
||||
peertube\.tronic-studio\.com|
|
||||
peertube\.public\.cat|
|
||||
peertube\.metalbanana\.net|
|
||||
video\.1000i100\.fr|
|
||||
peertube\.alter-nativ-voll\.de|
|
||||
tube\.pasa\.tf|
|
||||
tube\.worldofhauru\.xyz|
|
||||
pt\.kamp\.site|
|
||||
peertube\.teleassist\.fr|
|
||||
videos\.mleduc\.xyz|
|
||||
conf\.tube|
|
||||
media\.privacyinternational\.org|
|
||||
pt\.forty-two\.nl|
|
||||
video\.halle-leaks\.de|
|
||||
video\.grosskopfgames\.de|
|
||||
peertube\.schaeferit\.de|
|
||||
peertube\.jackbot\.fr|
|
||||
tube\.extinctionrebellion\.fr|
|
||||
peertube\.f-si\.org|
|
||||
video\.subak\.ovh|
|
||||
videos\.koweb\.fr|
|
||||
peertube\.zergy\.net|
|
||||
peertube\.roflcopter\.fr|
|
||||
peertube\.floss-marketing-school\.com|
|
||||
vloggers\.social|
|
||||
peertube\.iriseden\.eu|
|
||||
videos\.ubuntu-paris\.org|
|
||||
peertube\.mastodon\.host|
|
||||
armstube\.com|
|
||||
peertube\.s2s\.video|
|
||||
peertube\.lol|
|
||||
tube\.open-plug\.eu|
|
||||
open\.tube|
|
||||
peertube\.ch|
|
||||
peertube\.normandie-libre\.fr|
|
||||
peertube\.slat\.org|
|
||||
video\.lacaveatonton\.ovh|
|
||||
peertube\.uno|
|
||||
peertube\.servebeer\.com|
|
||||
peertube\.fedi\.quebec|
|
||||
tube\.h3z\.jp|
|
||||
tube\.plus200\.com|
|
||||
peertube\.eric\.ovh|
|
||||
tube\.metadocs\.cc|
|
||||
tube\.unmondemeilleur\.eu|
|
||||
gouttedeau\.space|
|
||||
video\.antirep\.net|
|
||||
nrop\.cant\.at|
|
||||
tube\.ksl-bmx\.de|
|
||||
tube\.plaf\.fr|
|
||||
tube\.tchncs\.de|
|
||||
video\.devinberg\.com|
|
||||
hitchtube\.fr|
|
||||
peertube\.kosebamse\.com|
|
||||
yunopeertube\.myddns\.me|
|
||||
peertube\.varney\.fr|
|
||||
peertube\.anon-kenkai\.com|
|
||||
tube\.maiti\.info|
|
||||
tubee\.fr|
|
||||
videos\.dinofly\.com|
|
||||
toobnix\.org|
|
||||
videotape\.me|
|
||||
voca\.tube|
|
||||
video\.heromuster\.com|
|
||||
video\.lemediatv\.fr|
|
||||
video\.up\.edu\.ph|
|
||||
balafon\.video|
|
||||
video\.ivel\.fr|
|
||||
thickrips\.cloud|
|
||||
pt\.laurentkruger\.fr|
|
||||
video\.monarch-pass\.net|
|
||||
peertube\.artica\.center|
|
||||
video\.alternanet\.fr|
|
||||
indymotion\.fr|
|
||||
fanvid\.stopthatimp\.net|
|
||||
video\.farci\.org|
|
||||
v\.lesterpig\.com|
|
||||
video\.okaris\.de|
|
||||
tube\.pawelko\.net|
|
||||
peertube\.mablr\.org|
|
||||
tube\.fede\.re|
|
||||
pytu\.be|
|
||||
evertron\.tv|
|
||||
devtube\.dev-wiki\.de|
|
||||
raptube\.antipub\.org|
|
||||
video\.selea\.se|
|
||||
peertube\.mygaia\.org|
|
||||
video\.oh14\.de|
|
||||
peertube\.livingutopia\.org|
|
||||
peertube\.the-penguin\.de|
|
||||
tube\.thechangebook\.org|
|
||||
tube\.anjara\.eu|
|
||||
pt\.pube\.tk|
|
||||
video\.samedi\.pm|
|
||||
mplayer\.demouliere\.eu|
|
||||
widemus\.de|
|
||||
peertube\.me|
|
||||
peertube\.zapashcanon\.fr|
|
||||
video\.latavernedejohnjohn\.fr|
|
||||
peertube\.pcservice46\.fr|
|
||||
peertube\.mazzonetto\.eu|
|
||||
video\.irem\.univ-paris-diderot\.fr|
|
||||
video\.livecchi\.cloud|
|
||||
alttube\.fr|
|
||||
video\.coop\.tools|
|
||||
video\.cabane-libre\.org|
|
||||
peertube\.openstreetmap\.fr|
|
||||
videos\.alolise\.org|
|
||||
irrsinn\.video|
|
||||
video\.antopie\.org|
|
||||
scitech\.video|
|
||||
tube2\.nemsia\.org|
|
||||
video\.amic37\.fr|
|
||||
peertube\.freeforge\.eu|
|
||||
video\.arbitrarion\.com|
|
||||
video\.datsemultimedia\.com|
|
||||
stoptrackingus\.tv|
|
||||
peertube\.ricostrongxxx\.com|
|
||||
docker\.videos\.lecygnenoir\.info|
|
||||
peertube\.togart\.de|
|
||||
tube\.postblue\.info|
|
||||
videos\.domainepublic\.net|
|
||||
peertube\.cyber-tribal\.com|
|
||||
video\.gresille\.org|
|
||||
peertube\.dsmouse\.net|
|
||||
cinema\.yunohost\.support|
|
||||
tube\.theocevaer\.fr|
|
||||
repro\.video|
|
||||
tube\.4aem\.com|
|
||||
quaziinc\.com|
|
||||
peertube\.metawurst\.space|
|
||||
videos\.wakapo\.com|
|
||||
video\.ploud\.fr|
|
||||
video\.freeradical\.zone|
|
||||
tube\.valinor\.fr|
|
||||
refuznik\.video|
|
||||
pt\.kircheneuenburg\.de|
|
||||
peertube\.asrun\.eu|
|
||||
peertube\.lagob\.fr|
|
||||
videos\.side-ways\.net|
|
||||
91video\.online|
|
||||
video\.valme\.io|
|
||||
video\.taboulisme\.com|
|
||||
videos-libr\.es|
|
||||
tv\.mooh\.fr|
|
||||
nuage\.acostey\.fr|
|
||||
video\.monsieur-a\.fr|
|
||||
peertube\.librelois\.fr|
|
||||
videos\.pair2jeux\.tube|
|
||||
videos\.pueseso\.club|
|
||||
peer\.mathdacloud\.ovh|
|
||||
media\.assassinate-you\.net|
|
||||
vidcommons\.org|
|
||||
ptube\.rousset\.nom\.fr|
|
||||
tube\.cyano\.at|
|
||||
videos\.squat\.net|
|
||||
video\.iphodase\.fr|
|
||||
peertube\.makotoworkshop\.org|
|
||||
peertube\.serveur\.slv-valbonne\.fr|
|
||||
vault\.mle\.party|
|
||||
hostyour\.tv|
|
||||
videos\.hack2g2\.fr|
|
||||
libre\.tube|
|
||||
pire\.artisanlogiciel\.net|
|
||||
videos\.numerique-en-commun\.fr|
|
||||
video\.netsyms\.com|
|
||||
video\.die-partei\.social|
|
||||
video\.writeas\.org|
|
||||
peertube\.swarm\.solvingmaz\.es|
|
||||
tube\.pericoloso\.ovh|
|
||||
watching\.cypherpunk\.observer|
|
||||
videos\.adhocmusic\.com|
|
||||
tube\.rfc1149\.net|
|
||||
peertube\.librelabucm\.org|
|
||||
videos\.numericoop\.fr|
|
||||
peertube\.koehn\.com|
|
||||
peertube\.anarchmusicall\.net|
|
||||
tube\.kampftoast\.de|
|
||||
vid\.y-y\.li|
|
||||
peertube\.xtenz\.xyz|
|
||||
diode\.zone|
|
||||
tube\.egf\.mn|
|
||||
peertube\.nomagic\.uk|
|
||||
visionon\.tv|
|
||||
videos\.koumoul\.com|
|
||||
video\.rastapuls\.com|
|
||||
video\.mantlepro\.com|
|
||||
video\.deadsuperhero\.com|
|
||||
peertube\.musicstudio\.pro|
|
||||
peertube\.we-keys\.fr|
|
||||
artitube\.artifaille\.fr|
|
||||
peertube\.ethernia\.net|
|
||||
tube\.midov\.pl|
|
||||
peertube\.fr|
|
||||
watch\.snoot\.tube|
|
||||
peertube\.donnadieu\.fr|
|
||||
argos\.aquilenet\.fr|
|
||||
tube\.nemsia\.org|
|
||||
tube\.bruniau\.net|
|
||||
videos\.darckoune\.moe|
|
||||
tube\.traydent\.info|
|
||||
dev\.videos\.lecygnenoir\.info|
|
||||
peertube\.nayya\.org|
|
||||
peertube\.live|
|
||||
peertube\.mofgao\.space|
|
||||
video\.lequerrec\.eu|
|
||||
peertube\.amicale\.net|
|
||||
aperi\.tube|
|
||||
tube\.ac-lyon\.fr|
|
||||
video\.lw1\.at|
|
||||
www\.yiny\.org|
|
||||
videos\.pofilo\.fr|
|
||||
tube\.lou\.lt|
|
||||
choob\.h\.etbus\.ch|
|
||||
tube\.hoga\.fr|
|
||||
peertube\.heberge\.fr|
|
||||
video\.obermui\.de|
|
||||
videos\.cloudfrancois\.fr|
|
||||
betamax\.video|
|
||||
video\.typica\.us|
|
||||
tube\.piweb\.be|
|
||||
video\.blender\.org|
|
||||
peertube\.cat|
|
||||
tube\.kdy\.ch|
|
||||
pe\.ertu\.be|
|
||||
peertube\.social|
|
||||
videos\.lescommuns\.org|
|
||||
tv\.datamol\.org|
|
||||
videonaute\.fr|
|
||||
dialup\.express|
|
||||
peertube\.nogafa\.org|
|
||||
peertube\.pl|
|
||||
megatube\.lilomoino\.fr|
|
||||
peertube\.tamanoir\.foucry\.net|
|
||||
peertube\.inapurna\.org|
|
||||
peertube\.netzspielplatz\.de|
|
||||
video\.deadsuperhero\.com|
|
||||
peertube\.devosi\.org|
|
||||
peertube\.1312\.media|
|
||||
tube\.worldofhauru\.xyz|
|
||||
tube\.bootlicker\.party|
|
||||
skeptikon\.fr|
|
||||
peertube\.geekshell\.fr|
|
||||
tube\.opportunis\.me|
|
||||
peertube\.peshane\.net|
|
||||
video\.blueline\.mg|
|
||||
tube\.homecomputing\.fr|
|
||||
videos\.cloudfrancois\.fr|
|
||||
peertube\.viviers-fibre\.net|
|
||||
tube\.ouahpiti\.info|
|
||||
video\.tedomum\.net|
|
||||
video\.g3l\.org|
|
||||
fontube\.fr|
|
||||
peertube\.gaialabs\.ch|
|
||||
peertube\.extremely\.online|
|
||||
peertube\.public-infrastructure\.eu|
|
||||
tube\.kher\.nl|
|
||||
peertube\.qtg\.fr|
|
||||
tube\.22decembre\.eu|
|
||||
facegirl\.me|
|
||||
video\.migennes\.net|
|
||||
janny\.moe|
|
||||
tube\.p2p\.legal|
|
||||
video\.atlanti\.se|
|
||||
troll\.tv|
|
||||
peertube\.geekael\.fr|
|
||||
vid\.leotindall\.com|
|
||||
video\.anormallostpod\.ovh|
|
||||
p-tube\.h3z\.jp|
|
||||
tube\.darfweb\.eu|
|
||||
videos\.iut-orsay\.fr|
|
||||
peertube\.solidev\.net|
|
||||
videos\.symphonie-of-code\.fr|
|
||||
testtube\.ortg\.de|
|
||||
videos\.cemea\.org|
|
||||
peertube\.gwendalavir\.eu|
|
||||
video\.passageenseine\.fr|
|
||||
videos\.festivalparminous\.org|
|
||||
peertube\.touhoppai\.moe|
|
||||
peertube\.duckdns\.org|
|
||||
sikke\.fi|
|
||||
peertube\.mastodon\.host|
|
||||
firedragonvideos\.com|
|
||||
vidz\.dou\.bet|
|
||||
peertube\.koehn\.com|
|
||||
peer\.hostux\.social|
|
||||
share\.tube|
|
||||
peertube\.walkingmountains\.fr|
|
||||
medias\.libox\.fr|
|
||||
peertube\.moe|
|
||||
peertube\.xyz|
|
||||
jp\.peertube\.network|
|
||||
videos\.benpro\.fr|
|
||||
tube\.otter\.sh|
|
||||
peertube\.angristan\.xyz|
|
||||
peertube\.parleur\.net|
|
||||
peer\.ecutsa\.fr|
|
||||
peertube\.heraut\.eu|
|
||||
peertube\.tifox\.fr|
|
||||
peertube\.maly\.io|
|
||||
vod\.mochi\.academy|
|
||||
exode\.me|
|
||||
coste\.video|
|
||||
tube\.aquilenet\.fr|
|
||||
peertube\.gegeweb\.eu|
|
||||
framatube\.org|
|
||||
@@ -100,18 +404,11 @@ class PeerTubeIE(InfoExtractor):
|
||||
tube\.conferences-gesticulees\.net|
|
||||
peertube\.datagueule\.tv|
|
||||
video\.lqdn\.fr|
|
||||
meilleurtube\.delire\.party|
|
||||
tube\.mochi\.academy|
|
||||
peertube\.dav\.li|
|
||||
media\.zat\.im|
|
||||
pytu\.be|
|
||||
peertube\.valvin\.fr|
|
||||
peertube\.nsa\.ovh|
|
||||
video\.colibris-outilslibres\.org|
|
||||
video\.hispagatos\.org|
|
||||
tube\.svnet\.fr|
|
||||
peertube\.video|
|
||||
videos\.lecygnenoir\.info|
|
||||
peertube3\.cpy\.re|
|
||||
peertube2\.cpy\.re|
|
||||
videos\.tcit\.fr|
|
||||
@@ -126,7 +423,7 @@ class PeerTubeIE(InfoExtractor):
|
||||
(?P<id>%s)
|
||||
''' % (_INSTANCES_RE, _UUID_RE)
|
||||
_TESTS = [{
|
||||
'url': 'https://peertube.moe/videos/watch/2790feb0-8120-4e63-9af3-c943c69f5e6c',
|
||||
'url': 'https://peertube.cpy.re/videos/watch/2790feb0-8120-4e63-9af3-c943c69f5e6c',
|
||||
'md5': '80f24ff364cc9d333529506a263e7feb',
|
||||
'info_dict': {
|
||||
'id': '2790feb0-8120-4e63-9af3-c943c69f5e6c',
|
||||
|
@@ -15,7 +15,7 @@ from ..utils import (
|
||||
|
||||
|
||||
class PikselIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://player\.piksel\.com/v/(?P<id>[a-z0-9]+)'
|
||||
_VALID_URL = r'https?://player\.piksel\.com/v/(?:refid/[^/]+/prefid/)?(?P<id>[a-z0-9_]+)'
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://player.piksel.com/v/ums2867l',
|
||||
@@ -40,6 +40,11 @@ class PikselIE(InfoExtractor):
|
||||
'timestamp': 1486171129,
|
||||
'upload_date': '20170204'
|
||||
}
|
||||
},
|
||||
{
|
||||
# https://www3.nhk.or.jp/nhkworld/en/ondemand/video/2019240/
|
||||
'url': 'http://player.piksel.com/v/refid/nhkworld/prefid/nw_vod_v_en_2019_240_20190823233000_02_1566873477',
|
||||
'only_matching': True,
|
||||
}
|
||||
]
|
||||
|
||||
@@ -52,8 +57,11 @@ class PikselIE(InfoExtractor):
|
||||
return mobj.group('url')
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
display_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
video_id = self._search_regex(
|
||||
r'data-de-program-uuid=[\'"]([a-z0-9]+)',
|
||||
webpage, 'program uuid', default=display_id)
|
||||
app_token = self._search_regex([
|
||||
r'clientAPI\s*:\s*"([^"]+)"',
|
||||
r'data-de-api-key\s*=\s*"([^"]+)"'
|
||||
|
@@ -403,6 +403,15 @@ class PornHubUserIE(PornHubPlaylistBaseIE):
|
||||
|
||||
|
||||
class PornHubPagedPlaylistBaseIE(PornHubPlaylistBaseIE):
|
||||
@staticmethod
|
||||
def _has_more(webpage):
|
||||
return re.search(
|
||||
r'''(?x)
|
||||
<li[^>]+\bclass=["\']page_next|
|
||||
<link[^>]+\brel=["\']next|
|
||||
<button[^>]+\bid=["\']moreDataBtn
|
||||
''', webpage) is not None
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
host = mobj.group('host')
|
||||
@@ -411,13 +420,11 @@ class PornHubPagedPlaylistBaseIE(PornHubPlaylistBaseIE):
|
||||
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,
|
||||
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:
|
||||
@@ -547,18 +554,6 @@ class PornHubPagedVideoListIE(PornHubPagedPlaylistBaseIE):
|
||||
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)'
|
||||
@@ -572,11 +567,3 @@ class PornHubUserVideosUploadIE(PornHubPagedPlaylistBaseIE):
|
||||
'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)
|
||||
return '%s/ajax' % mobj.group('url')
|
||||
|
||||
@staticmethod
|
||||
def _has_more(webpage):
|
||||
return True
|
||||
|
@@ -1,70 +0,0 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
determine_ext,
|
||||
ExtractorError,
|
||||
urlencode_postdata,
|
||||
)
|
||||
|
||||
|
||||
class PromptFileIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?promptfile\.com/l/(?P<id>[0-9A-Z\-]+)'
|
||||
_TEST = {
|
||||
'url': 'http://www.promptfile.com/l/86D1CE8462-576CAAE416',
|
||||
'md5': '5a7e285a26e0d66d9a263fae91bc92ce',
|
||||
'info_dict': {
|
||||
'id': '86D1CE8462-576CAAE416',
|
||||
'ext': 'mp4',
|
||||
'title': 'oceans.mp4',
|
||||
'thumbnail': r're:^https?://.*\.jpg$',
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
if re.search(r'<div.+id="not_found_msg".+>(?!We are).+</div>[^-]', webpage) is not None:
|
||||
raise ExtractorError('Video %s does not exist' % video_id,
|
||||
expected=True)
|
||||
|
||||
chash = self._search_regex(
|
||||
r'val\("([^"]*)"\s*\+\s*\$\("#chash"\)', webpage, 'chash')
|
||||
fields = self._hidden_inputs(webpage)
|
||||
keys = list(fields.keys())
|
||||
chash_key = keys[0] if len(keys) == 1 else next(
|
||||
key for key in keys if key.startswith('cha'))
|
||||
fields[chash_key] = chash + fields[chash_key]
|
||||
|
||||
webpage = self._download_webpage(
|
||||
url, video_id, 'Downloading video page',
|
||||
data=urlencode_postdata(fields),
|
||||
headers={'Content-type': 'application/x-www-form-urlencoded'})
|
||||
|
||||
video_url = self._search_regex(
|
||||
(r'<a[^>]+href=(["\'])(?P<url>(?:(?!\1).)+)\1[^>]*>\s*Download File',
|
||||
r'<a[^>]+href=(["\'])(?P<url>https?://(?:www\.)?promptfile\.com/file/(?:(?!\1).)+)\1'),
|
||||
webpage, 'video url', group='url')
|
||||
title = self._html_search_regex(
|
||||
r'<span.+title="([^"]+)">', webpage, 'title')
|
||||
thumbnail = self._html_search_regex(
|
||||
r'<div id="player_overlay">.*button>.*?<img src="([^"]+)"',
|
||||
webpage, 'thumbnail', fatal=False, flags=re.DOTALL)
|
||||
|
||||
formats = [{
|
||||
'format_id': 'sd',
|
||||
'url': video_url,
|
||||
'ext': determine_ext(title),
|
||||
}]
|
||||
self._sort_formats(formats)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'thumbnail': thumbnail,
|
||||
'formats': formats,
|
||||
}
|
@@ -6,6 +6,7 @@ from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
int_or_none,
|
||||
merge_dicts,
|
||||
str_to_int,
|
||||
unified_strdate,
|
||||
url_or_none,
|
||||
@@ -45,11 +46,14 @@ class RedTubeIE(InfoExtractor):
|
||||
if any(s in webpage for s in ['video-deleted-info', '>This video has been removed']):
|
||||
raise ExtractorError('Video %s has been removed' % video_id, expected=True)
|
||||
|
||||
title = self._html_search_regex(
|
||||
(r'<h(\d)[^>]+class="(?:video_title_text|videoTitle)[^"]*">(?P<title>(?:(?!\1).)+)</h\1>',
|
||||
r'(?:videoTitle|title)\s*:\s*(["\'])(?P<title>(?:(?!\1).)+)\1',),
|
||||
webpage, 'title', group='title',
|
||||
default=None) or self._og_search_title(webpage)
|
||||
info = self._search_json_ld(webpage, video_id, default={})
|
||||
|
||||
if not info.get('title'):
|
||||
info['title'] = self._html_search_regex(
|
||||
(r'<h(\d)[^>]+class="(?:video_title_text|videoTitle)[^"]*">(?P<title>(?:(?!\1).)+)</h\1>',
|
||||
r'(?:videoTitle|title)\s*:\s*(["\'])(?P<title>(?:(?!\1).)+)\1',),
|
||||
webpage, 'title', group='title',
|
||||
default=None) or self._og_search_title(webpage)
|
||||
|
||||
formats = []
|
||||
sources = self._parse_json(
|
||||
@@ -88,28 +92,28 @@ class RedTubeIE(InfoExtractor):
|
||||
|
||||
thumbnail = self._og_search_thumbnail(webpage)
|
||||
upload_date = unified_strdate(self._search_regex(
|
||||
r'<span[^>]+>ADDED ([^<]+)<',
|
||||
webpage, 'upload date', fatal=False))
|
||||
r'<span[^>]+>(?:ADDED|Published on) ([^<]+)<',
|
||||
webpage, 'upload date', default=None))
|
||||
duration = int_or_none(self._og_search_property(
|
||||
'video:duration', webpage, default=None) or self._search_regex(
|
||||
r'videoDuration\s*:\s*(\d+)', webpage, 'duration', default=None))
|
||||
view_count = str_to_int(self._search_regex(
|
||||
(r'<div[^>]*>Views</div>\s*<div[^>]*>\s*([\d,.]+)',
|
||||
r'<span[^>]*>VIEWS</span>\s*</td>\s*<td>\s*([\d,.]+)'),
|
||||
webpage, 'view count', fatal=False))
|
||||
r'<span[^>]*>VIEWS</span>\s*</td>\s*<td>\s*([\d,.]+)',
|
||||
r'<span[^>]+\bclass=["\']video_view_count[^>]*>\s*([\d,.]+)'),
|
||||
webpage, 'view count', default=None))
|
||||
|
||||
# No self-labeling, but they describe themselves as
|
||||
# "Home of Videos Porno"
|
||||
age_limit = 18
|
||||
|
||||
return {
|
||||
return merge_dicts(info, {
|
||||
'id': video_id,
|
||||
'ext': 'mp4',
|
||||
'title': title,
|
||||
'thumbnail': thumbnail,
|
||||
'upload_date': upload_date,
|
||||
'duration': duration,
|
||||
'view_count': view_count,
|
||||
'age_limit': age_limit,
|
||||
'formats': formats,
|
||||
}
|
||||
})
|
||||
|
@@ -1,72 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
int_or_none,
|
||||
)
|
||||
|
||||
|
||||
class ServingSysIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:[^.]+\.)?serving-sys\.com/BurstingPipe/adServer\.bs\?.*?&pli=(?P<id>[0-9]+)'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://bs.serving-sys.com/BurstingPipe/adServer.bs?cn=is&c=23&pl=VAST&pli=5349193&PluID=0&pos=7135&ord=[timestamp]&cim=1?',
|
||||
'info_dict': {
|
||||
'id': '5349193',
|
||||
'title': 'AdAPPter_Hyundai_demo',
|
||||
},
|
||||
'playlist': [{
|
||||
'md5': 'baed851342df6846eb8677a60a011a0f',
|
||||
'info_dict': {
|
||||
'id': '29955898',
|
||||
'ext': 'flv',
|
||||
'title': 'AdAPPter_Hyundai_demo (1)',
|
||||
'duration': 74,
|
||||
'tbr': 1378,
|
||||
'width': 640,
|
||||
'height': 400,
|
||||
},
|
||||
}, {
|
||||
'md5': '979b4da2655c4bc2d81aeb915a8c5014',
|
||||
'info_dict': {
|
||||
'id': '29907998',
|
||||
'ext': 'flv',
|
||||
'title': 'AdAPPter_Hyundai_demo (2)',
|
||||
'duration': 34,
|
||||
'width': 854,
|
||||
'height': 480,
|
||||
'tbr': 516,
|
||||
},
|
||||
}],
|
||||
'params': {
|
||||
'playlistend': 2,
|
||||
},
|
||||
'_skip': 'Blocked in the US [sic]',
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
pl_id = self._match_id(url)
|
||||
vast_doc = self._download_xml(url, pl_id)
|
||||
|
||||
title = vast_doc.find('.//AdTitle').text
|
||||
media = vast_doc.find('.//MediaFile').text
|
||||
info_url = self._search_regex(r'&adData=([^&]+)&', media, 'info URL')
|
||||
|
||||
doc = self._download_xml(info_url, pl_id, 'Downloading video info')
|
||||
entries = [{
|
||||
'_type': 'video',
|
||||
'id': a.attrib['id'],
|
||||
'title': '%s (%s)' % (title, a.attrib['assetID']),
|
||||
'url': a.attrib['URL'],
|
||||
'duration': int_or_none(a.attrib.get('length')),
|
||||
'tbr': int_or_none(a.attrib.get('bitrate')),
|
||||
'height': int_or_none(a.attrib.get('height')),
|
||||
'width': int_or_none(a.attrib.get('width')),
|
||||
} for a in doc.findall('.//AdditionalAssets/asset')]
|
||||
|
||||
return {
|
||||
'_type': 'playlist',
|
||||
'id': pl_id,
|
||||
'title': title,
|
||||
'entries': entries,
|
||||
}
|
@@ -48,6 +48,16 @@ class TeachableBaseIE(InfoExtractor):
|
||||
'https://%s/sign_in' % site, None,
|
||||
'Downloading %s login page' % site)
|
||||
|
||||
def is_logged(webpage):
|
||||
return any(re.search(p, webpage) for p in (
|
||||
r'class=["\']user-signout',
|
||||
r'<a[^>]+\bhref=["\']/sign_out',
|
||||
r'Log\s+[Oo]ut\s*<'))
|
||||
|
||||
if is_logged(login_page):
|
||||
self._logged_in = True
|
||||
return
|
||||
|
||||
login_url = compat_str(urlh.geturl())
|
||||
|
||||
login_form = self._hidden_inputs(login_page)
|
||||
@@ -78,10 +88,7 @@ class TeachableBaseIE(InfoExtractor):
|
||||
'Go to https://%s/ and accept.' % (site, site), expected=True)
|
||||
|
||||
# Successful login
|
||||
if any(re.search(p, response) for p in (
|
||||
r'class=["\']user-signout',
|
||||
r'<a[^>]+\bhref=["\']/sign_out',
|
||||
r'>\s*Log out\s*<')):
|
||||
if is_logged(response):
|
||||
self._logged_in = True
|
||||
return
|
||||
|
||||
|
@@ -182,20 +182,29 @@ class TEDIE(InfoExtractor):
|
||||
|
||||
title = talk_info['title'].strip()
|
||||
|
||||
native_downloads = try_get(
|
||||
talk_info,
|
||||
(lambda x: x['downloads']['nativeDownloads'],
|
||||
lambda x: x['nativeDownloads']),
|
||||
dict) or {}
|
||||
downloads = talk_info.get('downloads') or {}
|
||||
native_downloads = downloads.get('nativeDownloads') or talk_info.get('nativeDownloads') or {}
|
||||
|
||||
formats = [{
|
||||
'url': format_url,
|
||||
'format_id': format_id,
|
||||
'format': format_id,
|
||||
} for (format_id, format_url) in native_downloads.items() if format_url is not None]
|
||||
|
||||
subtitled_downloads = downloads.get('subtitledDownloads') or {}
|
||||
for lang, subtitled_download in subtitled_downloads.items():
|
||||
for q in self._NATIVE_FORMATS:
|
||||
q_url = subtitled_download.get(q)
|
||||
if not q_url:
|
||||
continue
|
||||
formats.append({
|
||||
'url': q_url,
|
||||
'format_id': '%s-%s' % (q, lang),
|
||||
'language': lang,
|
||||
})
|
||||
|
||||
if formats:
|
||||
for f in formats:
|
||||
finfo = self._NATIVE_FORMATS.get(f['format_id'])
|
||||
finfo = self._NATIVE_FORMATS.get(f['format_id'].split('-')[0])
|
||||
if finfo:
|
||||
f.update(finfo)
|
||||
|
||||
@@ -215,34 +224,7 @@ class TEDIE(InfoExtractor):
|
||||
|
||||
http_url = None
|
||||
for format_id, resources in resources_.items():
|
||||
if format_id == 'h264':
|
||||
for resource in resources:
|
||||
h264_url = resource.get('file')
|
||||
if not h264_url:
|
||||
continue
|
||||
bitrate = int_or_none(resource.get('bitrate'))
|
||||
formats.append({
|
||||
'url': h264_url,
|
||||
'format_id': '%s-%sk' % (format_id, bitrate),
|
||||
'tbr': bitrate,
|
||||
})
|
||||
if re.search(r'\d+k', h264_url):
|
||||
http_url = h264_url
|
||||
elif format_id == 'rtmp':
|
||||
streamer = talk_info.get('streamer')
|
||||
if not streamer:
|
||||
continue
|
||||
for resource in resources:
|
||||
formats.append({
|
||||
'format_id': '%s-%s' % (format_id, resource.get('name')),
|
||||
'url': streamer,
|
||||
'play_path': resource['file'],
|
||||
'ext': 'flv',
|
||||
'width': int_or_none(resource.get('width')),
|
||||
'height': int_or_none(resource.get('height')),
|
||||
'tbr': int_or_none(resource.get('bitrate')),
|
||||
})
|
||||
elif format_id == 'hls':
|
||||
if format_id == 'hls':
|
||||
if not isinstance(resources, dict):
|
||||
continue
|
||||
stream_url = url_or_none(resources.get('stream'))
|
||||
@@ -251,6 +233,36 @@ class TEDIE(InfoExtractor):
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
stream_url, video_name, 'mp4', m3u8_id=format_id,
|
||||
fatal=False))
|
||||
else:
|
||||
if not isinstance(resources, list):
|
||||
continue
|
||||
if format_id == 'h264':
|
||||
for resource in resources:
|
||||
h264_url = resource.get('file')
|
||||
if not h264_url:
|
||||
continue
|
||||
bitrate = int_or_none(resource.get('bitrate'))
|
||||
formats.append({
|
||||
'url': h264_url,
|
||||
'format_id': '%s-%sk' % (format_id, bitrate),
|
||||
'tbr': bitrate,
|
||||
})
|
||||
if re.search(r'\d+k', h264_url):
|
||||
http_url = h264_url
|
||||
elif format_id == 'rtmp':
|
||||
streamer = talk_info.get('streamer')
|
||||
if not streamer:
|
||||
continue
|
||||
for resource in resources:
|
||||
formats.append({
|
||||
'format_id': '%s-%s' % (format_id, resource.get('name')),
|
||||
'url': streamer,
|
||||
'play_path': resource['file'],
|
||||
'ext': 'flv',
|
||||
'width': int_or_none(resource.get('width')),
|
||||
'height': int_or_none(resource.get('height')),
|
||||
'tbr': int_or_none(resource.get('bitrate')),
|
||||
})
|
||||
|
||||
m3u8_formats = list(filter(
|
||||
lambda f: f.get('protocol') == 'm3u8' and f.get('vcodec') != 'none',
|
||||
|
@@ -7,6 +7,7 @@ from ..utils import (
|
||||
int_or_none,
|
||||
smuggle_url,
|
||||
try_get,
|
||||
unified_timestamp,
|
||||
)
|
||||
|
||||
|
||||
@@ -22,7 +23,13 @@ class TeleQuebecBaseIE(InfoExtractor):
|
||||
|
||||
|
||||
class TeleQuebecIE(TeleQuebecBaseIE):
|
||||
_VALID_URL = r'https?://zonevideo\.telequebec\.tv/media/(?P<id>\d+)'
|
||||
_VALID_URL = r'''(?x)
|
||||
https?://
|
||||
(?:
|
||||
zonevideo\.telequebec\.tv/media|
|
||||
coucou\.telequebec\.tv/videos
|
||||
)/(?P<id>\d+)
|
||||
'''
|
||||
_TESTS = [{
|
||||
# available till 01.01.2023
|
||||
'url': 'http://zonevideo.telequebec.tv/media/37578/un-petit-choc-et-puis-repart/un-chef-a-la-cabane',
|
||||
@@ -41,6 +48,9 @@ class TeleQuebecIE(TeleQuebecBaseIE):
|
||||
# no description
|
||||
'url': 'http://zonevideo.telequebec.tv/media/30261',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://coucou.telequebec.tv/videos/41788/idee-de-genie/l-heure-du-bain',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
@@ -61,6 +71,52 @@ class TeleQuebecIE(TeleQuebecBaseIE):
|
||||
return info
|
||||
|
||||
|
||||
class TeleQuebecSquatIE(InfoExtractor):
|
||||
_VALID_URL = r'https://squat\.telequebec\.tv/videos/(?P<id>\d+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://squat.telequebec.tv/videos/9314',
|
||||
'info_dict': {
|
||||
'id': 'd59ae78112d542e793d83cc9d3a5b530',
|
||||
'ext': 'mp4',
|
||||
'title': 'Poupeflekta',
|
||||
'description': 'md5:2f0718f8d2f8fece1646ee25fb7bce75',
|
||||
'duration': 1351,
|
||||
'timestamp': 1569057600,
|
||||
'upload_date': '20190921',
|
||||
'series': 'Miraculous : Les Aventures de Ladybug et Chat Noir',
|
||||
'season': 'Saison 3',
|
||||
'season_number': 3,
|
||||
'episode_number': 57,
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
video = self._download_json(
|
||||
'https://squat.api.telequebec.tv/v1/videos/%s' % video_id,
|
||||
video_id)
|
||||
|
||||
media_id = video['sourceId']
|
||||
|
||||
return {
|
||||
'_type': 'url_transparent',
|
||||
'url': 'http://zonevideo.telequebec.tv/media/%s' % media_id,
|
||||
'ie_key': TeleQuebecIE.ie_key(),
|
||||
'id': media_id,
|
||||
'title': video.get('titre'),
|
||||
'description': video.get('description'),
|
||||
'timestamp': unified_timestamp(video.get('datePublication')),
|
||||
'series': video.get('container'),
|
||||
'season': video.get('saison'),
|
||||
'season_number': int_or_none(video.get('noSaison')),
|
||||
'episode_number': int_or_none(video.get('episode')),
|
||||
}
|
||||
|
||||
|
||||
class TeleQuebecEmissionIE(TeleQuebecBaseIE):
|
||||
_VALID_URL = r'''(?x)
|
||||
https?://
|
||||
|
@@ -72,8 +72,13 @@ class TV4IE(InfoExtractor):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
info = self._download_json(
|
||||
'http://www.tv4play.se/player/assets/%s.json' % video_id,
|
||||
video_id, 'Downloading video info JSON')
|
||||
'https://playback-api.b17g.net/asset/%s' % video_id,
|
||||
video_id, 'Downloading video info JSON', query={
|
||||
'service': 'tv4',
|
||||
'device': 'browser',
|
||||
'protocol': 'hls,dash',
|
||||
'drm': 'widevine',
|
||||
})['metadata']
|
||||
|
||||
title = info['title']
|
||||
|
||||
@@ -111,5 +116,9 @@ class TV4IE(InfoExtractor):
|
||||
'timestamp': parse_iso8601(info.get('broadcast_date_time')),
|
||||
'duration': int_or_none(info.get('duration')),
|
||||
'thumbnail': info.get('image'),
|
||||
'is_live': info.get('is_live') is True,
|
||||
'is_live': info.get('isLive') is True,
|
||||
'series': info.get('seriesTitle'),
|
||||
'season_number': int_or_none(info.get('seasonNumber')),
|
||||
'episode': info.get('episodeTitle'),
|
||||
'episode_number': int_or_none(info.get('episodeNumber')),
|
||||
}
|
||||
|
@@ -248,7 +248,7 @@ class TwitchVodIE(TwitchItemBaseIE):
|
||||
https?://
|
||||
(?:
|
||||
(?:(?:www|go|m)\.)?twitch\.tv/(?:[^/]+/v(?:ideo)?|videos)/|
|
||||
player\.twitch\.tv/\?.*?\bvideo=v
|
||||
player\.twitch\.tv/\?.*?\bvideo=v?
|
||||
)
|
||||
(?P<id>\d+)
|
||||
'''
|
||||
@@ -306,6 +306,9 @@ class TwitchVodIE(TwitchItemBaseIE):
|
||||
}, {
|
||||
'url': 'https://www.twitch.tv/northernlion/video/291940395',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://player.twitch.tv/?video=480452374',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
@@ -1,157 +0,0 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
parse_iso8601,
|
||||
sanitized_Request,
|
||||
)
|
||||
|
||||
|
||||
class VesselIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?vessel\.com/(?:videos|embed)/(?P<id>[0-9a-zA-Z-_]+)'
|
||||
_API_URL_TEMPLATE = 'https://www.vessel.com/api/view/items/%s'
|
||||
_LOGIN_URL = 'https://www.vessel.com/api/account/login'
|
||||
_NETRC_MACHINE = 'vessel'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.vessel.com/videos/HDN7G5UMs',
|
||||
'md5': '455cdf8beb71c6dd797fd2f3818d05c4',
|
||||
'info_dict': {
|
||||
'id': 'HDN7G5UMs',
|
||||
'ext': 'mp4',
|
||||
'title': 'Nvidia GeForce GTX Titan X - The Best Video Card on the Market?',
|
||||
'thumbnail': r're:^https?://.*\.jpg$',
|
||||
'upload_date': '20150317',
|
||||
'description': 'Did Nvidia pull out all the stops on the Titan X, or does its performance leave something to be desired?',
|
||||
'timestamp': int,
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www.vessel.com/embed/G4U7gUJ6a?w=615&h=346',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.vessel.com/videos/F01_dsLj1',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.vessel.com/videos/RRX-sir-J',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
@staticmethod
|
||||
def _extract_urls(webpage):
|
||||
return [url for _, url in re.findall(
|
||||
r'<iframe[^>]+src=(["\'])((?:https?:)?//(?:www\.)?vessel\.com/embed/[0-9a-zA-Z-_]+.*?)\1',
|
||||
webpage)]
|
||||
|
||||
@staticmethod
|
||||
def make_json_request(url, data):
|
||||
payload = json.dumps(data).encode('utf-8')
|
||||
req = sanitized_Request(url, payload)
|
||||
req.add_header('Content-Type', 'application/json; charset=utf-8')
|
||||
return req
|
||||
|
||||
@staticmethod
|
||||
def find_assets(data, asset_type, asset_id=None):
|
||||
for asset in data.get('assets', []):
|
||||
if not asset.get('type') == asset_type:
|
||||
continue
|
||||
elif asset_id is not None and not asset.get('id') == asset_id:
|
||||
continue
|
||||
else:
|
||||
yield asset
|
||||
|
||||
def _check_access_rights(self, data):
|
||||
access_info = data.get('__view', {})
|
||||
if not access_info.get('allow_access', True):
|
||||
err_code = access_info.get('error_code') or ''
|
||||
if err_code == 'ITEM_PAID_ONLY':
|
||||
raise ExtractorError(
|
||||
'This video requires subscription.', expected=True)
|
||||
else:
|
||||
raise ExtractorError(
|
||||
'Access to this content is restricted. (%s said: %s)' % (self.IE_NAME, err_code), expected=True)
|
||||
|
||||
def _login(self):
|
||||
username, password = self._get_login_info()
|
||||
if username is None:
|
||||
return
|
||||
self.report_login()
|
||||
data = {
|
||||
'client_id': 'web',
|
||||
'type': 'password',
|
||||
'user_key': username,
|
||||
'password': password,
|
||||
}
|
||||
login_request = VesselIE.make_json_request(self._LOGIN_URL, data)
|
||||
self._download_webpage(login_request, None, False, 'Wrong login info')
|
||||
|
||||
def _real_initialize(self):
|
||||
self._login()
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
data = self._parse_json(self._search_regex(
|
||||
r'App\.bootstrapData\((.*?)\);', webpage, 'data'), video_id)
|
||||
asset_id = data['model']['data']['id']
|
||||
|
||||
req = VesselIE.make_json_request(
|
||||
self._API_URL_TEMPLATE % asset_id, {'client': 'web'})
|
||||
data = self._download_json(req, video_id)
|
||||
video_asset_id = data.get('main_video_asset')
|
||||
|
||||
self._check_access_rights(data)
|
||||
|
||||
try:
|
||||
video_asset = next(
|
||||
VesselIE.find_assets(data, 'video', asset_id=video_asset_id))
|
||||
except StopIteration:
|
||||
raise ExtractorError('No video assets found')
|
||||
|
||||
formats = []
|
||||
for f in video_asset.get('sources', []):
|
||||
location = f.get('location')
|
||||
if not location:
|
||||
continue
|
||||
name = f.get('name')
|
||||
if name == 'hls-index':
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
location, video_id, ext='mp4',
|
||||
entry_protocol='m3u8_native', m3u8_id='m3u8', fatal=False))
|
||||
elif name == 'dash-index':
|
||||
formats.extend(self._extract_mpd_formats(
|
||||
location, video_id, mpd_id='dash', fatal=False))
|
||||
else:
|
||||
formats.append({
|
||||
'format_id': name,
|
||||
'tbr': f.get('bitrate'),
|
||||
'height': f.get('height'),
|
||||
'width': f.get('width'),
|
||||
'url': location,
|
||||
})
|
||||
self._sort_formats(formats)
|
||||
|
||||
thumbnails = []
|
||||
for im_asset in VesselIE.find_assets(data, 'image'):
|
||||
thumbnails.append({
|
||||
'url': im_asset['location'],
|
||||
'width': im_asset.get('width', 0),
|
||||
'height': im_asset.get('height', 0),
|
||||
})
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': data['title'],
|
||||
'formats': formats,
|
||||
'thumbnails': thumbnails,
|
||||
'description': data.get('short_description'),
|
||||
'duration': data.get('duration'),
|
||||
'comment_count': data.get('comment_count'),
|
||||
'like_count': data.get('like_count'),
|
||||
'view_count': data.get('view_count'),
|
||||
'timestamp': parse_iso8601(data.get('released_at')),
|
||||
}
|
@@ -13,11 +13,12 @@ from ..utils import (
|
||||
js_to_json,
|
||||
parse_age_limit,
|
||||
parse_duration,
|
||||
try_get,
|
||||
)
|
||||
|
||||
|
||||
class ViewLiftBaseIE(InfoExtractor):
|
||||
_DOMAINS_REGEX = r'(?:snagfilms|snagxtreme|funnyforfree|kiddovid|winnersview|(?:monumental|lax)sportsnetwork|vayafilm)\.com|hoichoi\.tv'
|
||||
_DOMAINS_REGEX = r'(?:(?:main\.)?snagfilms|snagxtreme|funnyforfree|kiddovid|winnersview|(?:monumental|lax)sportsnetwork|vayafilm)\.com|hoichoi\.tv'
|
||||
|
||||
|
||||
class ViewLiftEmbedIE(ViewLiftBaseIE):
|
||||
@@ -113,7 +114,7 @@ class ViewLiftEmbedIE(ViewLiftBaseIE):
|
||||
|
||||
|
||||
class ViewLiftIE(ViewLiftBaseIE):
|
||||
_VALID_URL = r'https?://(?:www\.)?(?P<domain>%s)/(?:films/title|show|(?:news/)?videos?)/(?P<id>[^?#]+)' % ViewLiftBaseIE._DOMAINS_REGEX
|
||||
_VALID_URL = r'https?://(?:www\.)?(?P<domain>%s)(?:/(?:films/title|show|(?:news/)?videos?))?/(?P<id>[^?#]+)' % ViewLiftBaseIE._DOMAINS_REGEX
|
||||
_TESTS = [{
|
||||
'url': 'http://www.snagfilms.com/films/title/lost_for_life',
|
||||
'md5': '19844f897b35af219773fd63bdec2942',
|
||||
@@ -128,7 +129,7 @@ class ViewLiftIE(ViewLiftBaseIE):
|
||||
'categories': 'mincount:3',
|
||||
'age_limit': 14,
|
||||
'upload_date': '20150421',
|
||||
'timestamp': 1429656819,
|
||||
'timestamp': 1429656820,
|
||||
}
|
||||
}, {
|
||||
'url': 'http://www.snagfilms.com/show/the_world_cut_project/india',
|
||||
@@ -141,10 +142,26 @@ class ViewLiftIE(ViewLiftBaseIE):
|
||||
'description': 'md5:5c168c5a8f4719c146aad2e0dfac6f5f',
|
||||
'thumbnail': r're:^https?://.*\.jpg',
|
||||
'duration': 979,
|
||||
'categories': 'mincount:2',
|
||||
'timestamp': 1399478279,
|
||||
'upload_date': '20140507',
|
||||
}
|
||||
}, {
|
||||
'url': 'http://main.snagfilms.com/augie_alone/s_2_ep_12_love',
|
||||
'info_dict': {
|
||||
'id': '00000148-7b53-de26-a9fb-fbf306f70020',
|
||||
'display_id': 'augie_alone/s_2_ep_12_love',
|
||||
'ext': 'mp4',
|
||||
'title': 'Augie, Alone:S. 2 Ep. 12 - Love',
|
||||
'description': 'md5:db2a5c72d994f16a780c1eb353a8f403',
|
||||
'thumbnail': r're:^https?://.*\.jpg',
|
||||
'duration': 107,
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
}, {
|
||||
'url': 'http://main.snagfilms.com/films/title/the_freebie',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# Film is not playable in your area.
|
||||
'url': 'http://www.snagfilms.com/films/title/inside_mecca',
|
||||
@@ -162,6 +179,10 @@ class ViewLiftIE(ViewLiftBaseIE):
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
@classmethod
|
||||
def suitable(cls, url):
|
||||
return False if ViewLiftEmbedIE.suitable(url) else super(ViewLiftIE, cls).suitable(url)
|
||||
|
||||
def _real_extract(self, url):
|
||||
domain, display_id = re.match(self._VALID_URL, url).groups()
|
||||
|
||||
@@ -181,7 +202,21 @@ class ViewLiftIE(ViewLiftBaseIE):
|
||||
gist = content_data['gist']
|
||||
film_id = gist['id']
|
||||
title = gist['title']
|
||||
video_assets = content_data['streamingInfo']['videoAssets']
|
||||
video_assets = try_get(
|
||||
content_data, lambda x: x['streamingInfo']['videoAssets'], dict)
|
||||
if not video_assets:
|
||||
token = self._download_json(
|
||||
'https://prod-api.viewlift.com/identity/anonymous-token',
|
||||
film_id, 'Downloading authorization token',
|
||||
query={'site': 'snagfilms'})['authorizationToken']
|
||||
video_assets = self._download_json(
|
||||
'https://prod-api.viewlift.com/entitlement/video/status',
|
||||
film_id, headers={
|
||||
'Authorization': token,
|
||||
'Referer': url,
|
||||
}, query={
|
||||
'id': film_id
|
||||
})['video']['streamingInfo']['videoAssets']
|
||||
|
||||
formats = []
|
||||
mpeg_video_assets = video_assets.get('mpeg') or []
|
||||
@@ -241,8 +276,9 @@ class ViewLiftIE(ViewLiftBaseIE):
|
||||
if category.get('title')]
|
||||
break
|
||||
else:
|
||||
title = self._search_regex(
|
||||
r'itemprop="title">([^<]+)<', webpage, 'title')
|
||||
title = self._html_search_regex(
|
||||
(r'itemprop="title">([^<]+)<',
|
||||
r'(?s)itemprop="title">(.+?)<div'), webpage, 'title')
|
||||
description = self._html_search_regex(
|
||||
r'(?s)<div itemprop="description" class="film-synopsis-inner ">(.+?)</div>',
|
||||
webpage, 'description', default=None) or self._og_search_description(webpage)
|
||||
|
@@ -1,217 +0,0 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..compat import (
|
||||
compat_HTTPError,
|
||||
compat_urllib_parse_unquote,
|
||||
)
|
||||
from ..utils import (
|
||||
determine_ext,
|
||||
ExtractorError,
|
||||
int_or_none,
|
||||
parse_iso8601,
|
||||
sanitized_Request,
|
||||
HEADRequest,
|
||||
url_basename,
|
||||
)
|
||||
|
||||
|
||||
class ViewsterIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?viewster\.com/(?:serie|movie)/(?P<id>\d+-\d+-\d+)'
|
||||
_TESTS = [{
|
||||
# movie, Type=Movie
|
||||
'url': 'http://www.viewster.com/movie/1140-11855-000/the-listening-project/',
|
||||
'md5': 'e642d1b27fcf3a4ffa79f194f5adde36',
|
||||
'info_dict': {
|
||||
'id': '1140-11855-000',
|
||||
'ext': 'mp4',
|
||||
'title': 'The listening Project',
|
||||
'description': 'md5:bac720244afd1a8ea279864e67baa071',
|
||||
'timestamp': 1214870400,
|
||||
'upload_date': '20080701',
|
||||
'duration': 4680,
|
||||
},
|
||||
}, {
|
||||
# series episode, Type=Episode
|
||||
'url': 'http://www.viewster.com/serie/1284-19427-001/the-world-and-a-wall/',
|
||||
'md5': '9243079a8531809efe1b089db102c069',
|
||||
'info_dict': {
|
||||
'id': '1284-19427-001',
|
||||
'ext': 'mp4',
|
||||
'title': 'The World and a Wall',
|
||||
'description': 'md5:24814cf74d3453fdf5bfef9716d073e3',
|
||||
'timestamp': 1428192000,
|
||||
'upload_date': '20150405',
|
||||
'duration': 1500,
|
||||
},
|
||||
}, {
|
||||
# serie, Type=Serie
|
||||
'url': 'http://www.viewster.com/serie/1303-19426-000/',
|
||||
'info_dict': {
|
||||
'id': '1303-19426-000',
|
||||
'title': 'Is It Wrong to Try to Pick up Girls in a Dungeon?',
|
||||
'description': 'md5:eeda9bef25b0d524b3a29a97804c2f11',
|
||||
},
|
||||
'playlist_count': 13,
|
||||
}, {
|
||||
# unfinished serie, no Type
|
||||
'url': 'http://www.viewster.com/serie/1284-19427-000/baby-steps-season-2/',
|
||||
'info_dict': {
|
||||
'id': '1284-19427-000',
|
||||
'title': 'Baby Steps—Season 2',
|
||||
'description': 'md5:e7097a8fc97151e25f085c9eb7a1cdb1',
|
||||
},
|
||||
'playlist_mincount': 16,
|
||||
}, {
|
||||
# geo restricted series
|
||||
'url': 'https://www.viewster.com/serie/1280-18794-002/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# geo restricted video
|
||||
'url': 'https://www.viewster.com/serie/1280-18794-002/what-is-extraterritoriality-lawo/',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
_ACCEPT_HEADER = 'application/json, text/javascript, */*; q=0.01'
|
||||
|
||||
def _download_json(self, url, video_id, note='Downloading JSON metadata', fatal=True, query={}):
|
||||
request = sanitized_Request(url)
|
||||
request.add_header('Accept', self._ACCEPT_HEADER)
|
||||
request.add_header('Auth-token', self._AUTH_TOKEN)
|
||||
return super(ViewsterIE, self)._download_json(request, video_id, note, fatal=fatal, query=query)
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
# Get 'api_token' cookie
|
||||
self._request_webpage(
|
||||
HEADRequest('http://www.viewster.com/'),
|
||||
video_id, headers=self.geo_verification_headers())
|
||||
cookies = self._get_cookies('http://www.viewster.com/')
|
||||
self._AUTH_TOKEN = compat_urllib_parse_unquote(cookies['api_token'].value)
|
||||
|
||||
info = self._download_json(
|
||||
'https://public-api.viewster.com/search/%s' % video_id,
|
||||
video_id, 'Downloading entry JSON')
|
||||
|
||||
entry_id = info.get('Id') or info['id']
|
||||
|
||||
# unfinished serie has no Type
|
||||
if info.get('Type') in ('Serie', None):
|
||||
try:
|
||||
episodes = self._download_json(
|
||||
'https://public-api.viewster.com/series/%s/episodes' % entry_id,
|
||||
video_id, 'Downloading series JSON')
|
||||
except ExtractorError as e:
|
||||
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 404:
|
||||
self.raise_geo_restricted()
|
||||
else:
|
||||
raise
|
||||
entries = [
|
||||
self.url_result(
|
||||
'http://www.viewster.com/movie/%s' % episode['OriginId'], 'Viewster')
|
||||
for episode in episodes]
|
||||
title = (info.get('Title') or info['Synopsis']['Title']).strip()
|
||||
description = info.get('Synopsis', {}).get('Detailed')
|
||||
return self.playlist_result(entries, video_id, title, description)
|
||||
|
||||
formats = []
|
||||
for language_set in info.get('LanguageSets', []):
|
||||
manifest_url = None
|
||||
m3u8_formats = []
|
||||
audio = language_set.get('Audio') or ''
|
||||
subtitle = language_set.get('Subtitle') or ''
|
||||
base_format_id = audio
|
||||
if subtitle:
|
||||
base_format_id += '-%s' % subtitle
|
||||
|
||||
def concat(suffix, sep='-'):
|
||||
return (base_format_id + '%s%s' % (sep, suffix)) if base_format_id else suffix
|
||||
|
||||
medias = self._download_json(
|
||||
'https://public-api.viewster.com/movies/%s/videos' % entry_id,
|
||||
video_id, fatal=False, query={
|
||||
'mediaTypes': ['application/f4m+xml', 'application/x-mpegURL', 'video/mp4'],
|
||||
'language': audio,
|
||||
'subtitle': subtitle,
|
||||
})
|
||||
if not medias:
|
||||
continue
|
||||
for media in medias:
|
||||
video_url = media.get('Uri')
|
||||
if not video_url:
|
||||
continue
|
||||
ext = determine_ext(video_url)
|
||||
if ext == 'f4m':
|
||||
manifest_url = video_url
|
||||
video_url += '&' if '?' in video_url else '?'
|
||||
video_url += 'hdcore=3.2.0&plugin=flowplayer-3.2.0.1'
|
||||
formats.extend(self._extract_f4m_formats(
|
||||
video_url, video_id, f4m_id=concat('hds')))
|
||||
elif ext == 'm3u8':
|
||||
manifest_url = video_url
|
||||
m3u8_formats = self._extract_m3u8_formats(
|
||||
video_url, video_id, 'mp4', m3u8_id=concat('hls'),
|
||||
fatal=False) # m3u8 sometimes fail
|
||||
if m3u8_formats:
|
||||
formats.extend(m3u8_formats)
|
||||
else:
|
||||
qualities_basename = self._search_regex(
|
||||
r'/([^/]+)\.csmil/',
|
||||
manifest_url, 'qualities basename', default=None)
|
||||
if not qualities_basename:
|
||||
continue
|
||||
QUALITIES_RE = r'((,\d+k)+,?)'
|
||||
qualities = self._search_regex(
|
||||
QUALITIES_RE, qualities_basename,
|
||||
'qualities', default=None)
|
||||
if not qualities:
|
||||
continue
|
||||
qualities = list(map(lambda q: int(q[:-1]), qualities.strip(',').split(',')))
|
||||
qualities.sort()
|
||||
http_template = re.sub(QUALITIES_RE, r'%dk', qualities_basename)
|
||||
http_url_basename = url_basename(video_url)
|
||||
if m3u8_formats:
|
||||
self._sort_formats(m3u8_formats)
|
||||
m3u8_formats = list(filter(
|
||||
lambda f: f.get('vcodec') != 'none', m3u8_formats))
|
||||
if len(qualities) == len(m3u8_formats):
|
||||
for q, m3u8_format in zip(qualities, m3u8_formats):
|
||||
f = m3u8_format.copy()
|
||||
f.update({
|
||||
'url': video_url.replace(http_url_basename, http_template % q),
|
||||
'format_id': f['format_id'].replace('hls', 'http'),
|
||||
'protocol': 'http',
|
||||
})
|
||||
formats.append(f)
|
||||
else:
|
||||
for q in qualities:
|
||||
formats.append({
|
||||
'url': video_url.replace(http_url_basename, http_template % q),
|
||||
'ext': 'mp4',
|
||||
'format_id': 'http-%d' % q,
|
||||
'tbr': q,
|
||||
})
|
||||
|
||||
if not formats and not info.get('VODSettings'):
|
||||
self.raise_geo_restricted()
|
||||
|
||||
self._sort_formats(formats)
|
||||
|
||||
synopsis = info.get('Synopsis') or {}
|
||||
# Prefer title outside synopsis since it's less messy
|
||||
title = (info.get('Title') or synopsis['Title']).strip()
|
||||
description = synopsis.get('Detailed') or (info.get('Synopsis') or {}).get('Short')
|
||||
duration = int_or_none(info.get('Duration'))
|
||||
timestamp = parse_iso8601(info.get('ReleaseDate'))
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'timestamp': timestamp,
|
||||
'duration': duration,
|
||||
'formats': formats,
|
||||
}
|
@@ -23,7 +23,6 @@ from ..utils import (
|
||||
NO_DEFAULT,
|
||||
OnDemandPagedList,
|
||||
parse_filesize,
|
||||
qualities,
|
||||
RegexNotFoundError,
|
||||
sanitized_Request,
|
||||
smuggle_url,
|
||||
@@ -211,6 +210,7 @@ class VimeoBaseInfoExtractor(InfoExtractor):
|
||||
video_uploader_url = owner.get('url')
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': self._live_title(video_title) if is_live else video_title,
|
||||
'uploader': owner.get('name'),
|
||||
'uploader_id': video_uploader_url.split('/')[-1] if video_uploader_url else None,
|
||||
@@ -730,7 +730,6 @@ class VimeoIE(VimeoBaseInfoExtractor):
|
||||
channel_url = 'https://vimeo.com/channels/%s' % channel_id if channel_id else None
|
||||
|
||||
info_dict = {
|
||||
'id': video_id,
|
||||
'formats': formats,
|
||||
'timestamp': unified_timestamp(timestamp),
|
||||
'description': video_description,
|
||||
@@ -939,7 +938,7 @@ class VimeoAlbumIE(VimeoChannelIE):
|
||||
def _fetch_page(self, album_id, authorizaion, hashed_pass, page):
|
||||
api_page = page + 1
|
||||
query = {
|
||||
'fields': 'link',
|
||||
'fields': 'link,uri',
|
||||
'page': api_page,
|
||||
'per_page': self._PAGE_SIZE,
|
||||
}
|
||||
@@ -954,7 +953,9 @@ class VimeoAlbumIE(VimeoChannelIE):
|
||||
link = video.get('link')
|
||||
if not link:
|
||||
continue
|
||||
yield self.url_result(link, VimeoIE.ie_key(), VimeoIE._match_id(link))
|
||||
uri = video.get('uri')
|
||||
video_id = self._search_regex(r'/videos/(\d+)', uri, 'video_id', default=None) if uri else None
|
||||
yield self.url_result(link, VimeoIE.ie_key(), video_id)
|
||||
|
||||
def _real_extract(self, url):
|
||||
album_id = self._match_id(url)
|
||||
@@ -1061,7 +1062,6 @@ class VimeoReviewIE(VimeoBaseInfoExtractor):
|
||||
if source_format:
|
||||
info_dict['formats'].append(source_format)
|
||||
self._vimeo_sort_formats(info_dict['formats'])
|
||||
info_dict['id'] = video_id
|
||||
return info_dict
|
||||
|
||||
|
||||
@@ -1115,94 +1115,17 @@ class VimeoLikesIE(VimeoChannelIE):
|
||||
return self._extract_videos(user_id, 'https://vimeo.com/%s/likes' % user_id)
|
||||
|
||||
|
||||
class VHXEmbedIE(InfoExtractor):
|
||||
class VHXEmbedIE(VimeoBaseInfoExtractor):
|
||||
IE_NAME = 'vhx:embed'
|
||||
_VALID_URL = r'https?://embed\.vhx\.tv/videos/(?P<id>\d+)'
|
||||
|
||||
def _call_api(self, video_id, access_token, path='', query=None):
|
||||
return self._download_json(
|
||||
'https://api.vhx.tv/videos/' + video_id + path, video_id, headers={
|
||||
'Authorization': 'Bearer ' + access_token,
|
||||
}, query=query)
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
credentials = self._parse_json(self._search_regex(
|
||||
r'(?s)credentials\s*:\s*({.+?}),', webpage,
|
||||
'config'), video_id, js_to_json)
|
||||
access_token = credentials['access_token']
|
||||
|
||||
query = {}
|
||||
for k, v in credentials.items():
|
||||
if k in ('authorization', 'authUserToken', 'ticket') and v and v != 'undefined':
|
||||
if k == 'authUserToken':
|
||||
query['auth_user_token'] = v
|
||||
else:
|
||||
query[k] = v
|
||||
files = self._call_api(video_id, access_token, '/files', query)
|
||||
|
||||
formats = []
|
||||
for f in files:
|
||||
href = try_get(f, lambda x: x['_links']['source']['href'])
|
||||
if not href:
|
||||
continue
|
||||
method = f.get('method')
|
||||
if method == 'hls':
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
href, video_id, 'mp4', 'm3u8_native',
|
||||
m3u8_id='hls', fatal=False))
|
||||
elif method == 'dash':
|
||||
formats.extend(self._extract_mpd_formats(
|
||||
href, video_id, mpd_id='dash', fatal=False))
|
||||
else:
|
||||
fmt = {
|
||||
'filesize': int_or_none(try_get(f, lambda x: x['size']['bytes'])),
|
||||
'format_id': 'http',
|
||||
'preference': 1,
|
||||
'url': href,
|
||||
'vcodec': f.get('codec'),
|
||||
}
|
||||
quality = f.get('quality')
|
||||
if quality:
|
||||
fmt.update({
|
||||
'format_id': 'http-' + quality,
|
||||
'height': int_or_none(self._search_regex(r'(\d+)p', quality, 'height', default=None)),
|
||||
})
|
||||
formats.append(fmt)
|
||||
self._sort_formats(formats)
|
||||
|
||||
video_data = self._call_api(video_id, access_token)
|
||||
title = video_data.get('title') or video_data['name']
|
||||
|
||||
subtitles = {}
|
||||
for subtitle in try_get(video_data, lambda x: x['tracks']['subtitles'], list) or []:
|
||||
lang = subtitle.get('srclang') or subtitle.get('label')
|
||||
for _link in subtitle.get('_links', {}).values():
|
||||
href = _link.get('href')
|
||||
if not href:
|
||||
continue
|
||||
subtitles.setdefault(lang, []).append({
|
||||
'url': href,
|
||||
})
|
||||
|
||||
q = qualities(['small', 'medium', 'large', 'source'])
|
||||
thumbnails = []
|
||||
for thumbnail_id, thumbnail_url in video_data.get('thumbnail', {}).items():
|
||||
thumbnails.append({
|
||||
'id': thumbnail_id,
|
||||
'url': thumbnail_url,
|
||||
'preference': q(thumbnail_id),
|
||||
})
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'description': video_data.get('description'),
|
||||
'duration': int_or_none(try_get(video_data, lambda x: x['duration']['seconds'])),
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
'thumbnails': thumbnails,
|
||||
'timestamp': unified_timestamp(video_data.get('created_at')),
|
||||
'view_count': int_or_none(video_data.get('plays_count')),
|
||||
}
|
||||
config_url = self._parse_json(self._search_regex(
|
||||
r'window\.OTTData\s*=\s*({.+})', webpage,
|
||||
'ott data'), video_id, js_to_json)['config_url']
|
||||
config = self._download_json(config_url, video_id)
|
||||
info = self._parse_config(config, video_id)
|
||||
self._vimeo_sort_formats(info['formats'])
|
||||
return info
|
||||
|
@@ -403,8 +403,17 @@ class VKIE(VKBaseIE):
|
||||
data = self._parse_json(
|
||||
self._search_regex(
|
||||
r'var\s+playerParams\s*=\s*({.+?})\s*;\s*\n', info_page,
|
||||
'player params'),
|
||||
video_id)['params'][0]
|
||||
'player params', default='{}'),
|
||||
video_id)
|
||||
if data:
|
||||
data = data['params'][0]
|
||||
|
||||
# <!--{...}
|
||||
if not data:
|
||||
data = self._parse_json(
|
||||
self._search_regex(
|
||||
r'<!--\s*({.+})', info_page, 'payload'),
|
||||
video_id)['payload'][-1][-1]['player']['params'][0]
|
||||
|
||||
title = unescapeHTML(data['md_title'])
|
||||
|
||||
|
@@ -1,54 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .common import InfoExtractor
|
||||
from .youtube import YoutubeIE
|
||||
|
||||
|
||||
class WimpIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?wimp\.com/(?P<id>[^/]+)'
|
||||
_TESTS = [{
|
||||
'url': 'http://www.wimp.com/maru-is-exhausted/',
|
||||
'md5': 'ee21217ffd66d058e8b16be340b74883',
|
||||
'info_dict': {
|
||||
'id': 'maru-is-exhausted',
|
||||
'ext': 'mp4',
|
||||
'title': 'Maru is exhausted.',
|
||||
'description': 'md5:57e099e857c0a4ea312542b684a869b8',
|
||||
}
|
||||
}, {
|
||||
'url': 'http://www.wimp.com/clowncar/',
|
||||
'md5': '5c31ad862a90dc5b1f023956faec13fe',
|
||||
'info_dict': {
|
||||
'id': 'cG4CEr2aiSg',
|
||||
'ext': 'webm',
|
||||
'title': 'Basset hound clown car...incredible!',
|
||||
'description': '5 of my Bassets crawled in this dog loo! www.bellinghambassets.com\n\nFor licensing/usage please contact: licensing(at)jukinmediadotcom',
|
||||
'upload_date': '20140303',
|
||||
'uploader': 'Gretchen Hoey',
|
||||
'uploader_id': 'gretchenandjeff1',
|
||||
},
|
||||
'add_ie': ['Youtube'],
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
youtube_id = self._search_regex(
|
||||
(r"videoId\s*:\s*[\"']([0-9A-Za-z_-]{11})[\"']",
|
||||
r'data-id=["\']([0-9A-Za-z_-]{11})'),
|
||||
webpage, 'video URL', default=None)
|
||||
if youtube_id:
|
||||
return self.url_result(youtube_id, YoutubeIE.ie_key())
|
||||
|
||||
info_dict = self._extract_jwplayer_data(
|
||||
webpage, video_id, require_title=False)
|
||||
|
||||
info_dict.update({
|
||||
'id': video_id,
|
||||
'title': self._og_search_title(webpage),
|
||||
'description': self._og_search_description(webpage),
|
||||
})
|
||||
|
||||
return info_dict
|
@@ -4,37 +4,64 @@ from __future__ import unicode_literals
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..compat import compat_chr
|
||||
from ..utils import (
|
||||
decode_packed_codes,
|
||||
determine_ext,
|
||||
ExtractorError,
|
||||
int_or_none,
|
||||
NO_DEFAULT,
|
||||
js_to_json,
|
||||
urlencode_postdata,
|
||||
)
|
||||
|
||||
|
||||
# based on openload_decode from 2bfeee69b976fe049761dd3012e30b637ee05a58
|
||||
def aa_decode(aa_code):
|
||||
symbol_table = [
|
||||
('7', '((゚ー゚) + (o^_^o))'),
|
||||
('6', '((o^_^o) +(o^_^o))'),
|
||||
('5', '((゚ー゚) + (゚Θ゚))'),
|
||||
('2', '((o^_^o) - (゚Θ゚))'),
|
||||
('4', '(゚ー゚)'),
|
||||
('3', '(o^_^o)'),
|
||||
('1', '(゚Θ゚)'),
|
||||
('0', '(c^_^o)'),
|
||||
]
|
||||
delim = '(゚Д゚)[゚ε゚]+'
|
||||
ret = ''
|
||||
for aa_char in aa_code.split(delim):
|
||||
for val, pat in symbol_table:
|
||||
aa_char = aa_char.replace(pat, val)
|
||||
aa_char = aa_char.replace('+ ', '')
|
||||
m = re.match(r'^\d+', aa_char)
|
||||
if m:
|
||||
ret += compat_chr(int(m.group(0), 8))
|
||||
else:
|
||||
m = re.match(r'^u([\da-f]+)', aa_char)
|
||||
if m:
|
||||
ret += compat_chr(int(m.group(1), 16))
|
||||
return ret
|
||||
|
||||
|
||||
class XFileShareIE(InfoExtractor):
|
||||
_SITES = (
|
||||
(r'daclips\.(?:in|com)', 'DaClips'),
|
||||
(r'filehoot\.com', 'FileHoot'),
|
||||
(r'gorillavid\.(?:in|com)', 'GorillaVid'),
|
||||
(r'movpod\.in', 'MovPod'),
|
||||
(r'powerwatch\.pw', 'PowerWatch'),
|
||||
(r'rapidvideo\.ws', 'Rapidvideo.ws'),
|
||||
(r'clipwatching\.com', 'ClipWatching'),
|
||||
(r'gounlimited\.to', 'GoUnlimited'),
|
||||
(r'govid\.me', 'GoVid'),
|
||||
(r'holavid\.com', 'HolaVid'),
|
||||
(r'streamty\.com', 'Streamty'),
|
||||
(r'thevideobee\.to', 'TheVideoBee'),
|
||||
(r'vidto\.(?:me|se)', 'Vidto'),
|
||||
(r'streamin\.to', 'Streamin.To'),
|
||||
(r'xvidstage\.com', 'XVIDSTAGE'),
|
||||
(r'vidabc\.com', 'Vid ABC'),
|
||||
(r'uqload\.com', 'Uqload'),
|
||||
(r'vidbom\.com', 'VidBom'),
|
||||
(r'vidlo\.us', 'vidlo'),
|
||||
(r'rapidvideo\.(?:cool|org)', 'RapidVideo.TV'),
|
||||
(r'fastvideo\.me', 'FastVideo.me'),
|
||||
(r'vidlocker\.xyz', 'VidLocker'),
|
||||
(r'vidshare\.tv', 'VidShare'),
|
||||
(r'vup\.to', 'VUp'),
|
||||
(r'xvideosharing\.com', 'XVideoSharing'),
|
||||
)
|
||||
|
||||
IE_DESC = 'XFileShare based sites: %s' % ', '.join(list(zip(*_SITES))[1])
|
||||
_VALID_URL = (r'https?://(?P<host>(?:www\.)?(?:%s))/(?:embed-)?(?P<id>[0-9a-zA-Z]+)'
|
||||
_VALID_URL = (r'https?://(?:www\.)?(?P<host>%s)/(?:embed-)?(?P<id>[0-9a-zA-Z]+)'
|
||||
% '|'.join(site for site in list(zip(*_SITES))[0]))
|
||||
|
||||
_FILE_NOT_FOUND_REGEXES = (
|
||||
@@ -43,82 +70,14 @@ class XFileShareIE(InfoExtractor):
|
||||
)
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'http://gorillavid.in/06y9juieqpmi',
|
||||
'md5': '5ae4a3580620380619678ee4875893ba',
|
||||
'url': 'http://xvideosharing.com/fq65f94nd2ve',
|
||||
'md5': '4181f63957e8fe90ac836fa58dc3c8a6',
|
||||
'info_dict': {
|
||||
'id': '06y9juieqpmi',
|
||||
'id': 'fq65f94nd2ve',
|
||||
'ext': 'mp4',
|
||||
'title': 'Rebecca Black My Moment Official Music Video Reaction-6GK87Rc8bzQ',
|
||||
'title': 'sample',
|
||||
'thumbnail': r're:http://.*\.jpg',
|
||||
},
|
||||
}, {
|
||||
'url': 'http://gorillavid.in/embed-z08zf8le23c6-960x480.html',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://daclips.in/3rso4kdn6f9m',
|
||||
'md5': '1ad8fd39bb976eeb66004d3a4895f106',
|
||||
'info_dict': {
|
||||
'id': '3rso4kdn6f9m',
|
||||
'ext': 'mp4',
|
||||
'title': 'Micro Pig piglets ready on 16th July 2009-bG0PdrCdxUc',
|
||||
'thumbnail': r're:http://.*\.jpg',
|
||||
}
|
||||
}, {
|
||||
'url': 'http://movpod.in/0wguyyxi1yca',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://filehoot.com/3ivfabn7573c.html',
|
||||
'info_dict': {
|
||||
'id': '3ivfabn7573c',
|
||||
'ext': 'mp4',
|
||||
'title': 'youtube-dl test video \'äBaW_jenozKc.mp4.mp4',
|
||||
'thumbnail': r're:http://.*\.jpg',
|
||||
},
|
||||
'skip': 'Video removed',
|
||||
}, {
|
||||
'url': 'http://vidto.me/ku5glz52nqe1.html',
|
||||
'info_dict': {
|
||||
'id': 'ku5glz52nqe1',
|
||||
'ext': 'mp4',
|
||||
'title': 'test'
|
||||
}
|
||||
}, {
|
||||
'url': 'http://powerwatch.pw/duecjibvicbu',
|
||||
'info_dict': {
|
||||
'id': 'duecjibvicbu',
|
||||
'ext': 'mp4',
|
||||
'title': 'Big Buck Bunny trailer',
|
||||
},
|
||||
}, {
|
||||
'url': 'http://xvidstage.com/e0qcnl03co6z',
|
||||
'info_dict': {
|
||||
'id': 'e0qcnl03co6z',
|
||||
'ext': 'mp4',
|
||||
'title': 'Chucky Prank 2015.mp4',
|
||||
},
|
||||
}, {
|
||||
# removed by administrator
|
||||
'url': 'http://xvidstage.com/amfy7atlkx25',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://vidabc.com/i8ybqscrphfv',
|
||||
'info_dict': {
|
||||
'id': 'i8ybqscrphfv',
|
||||
'ext': 'mp4',
|
||||
'title': 're:Beauty and the Beast 2017',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
}, {
|
||||
'url': 'http://www.rapidvideo.cool/b667kprndr8w',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.fastvideo.me/k8604r8nk8sn/FAST_FURIOUS_8_-_Trailer_italiano_ufficiale.mp4.html',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://vidto.se/1tx1pf6t12cg.html',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
@staticmethod
|
||||
@@ -131,10 +90,9 @@ class XFileShareIE(InfoExtractor):
|
||||
webpage)]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
host, video_id = re.match(self._VALID_URL, url).groups()
|
||||
|
||||
url = 'http://%s/%s' % (mobj.group('host'), video_id)
|
||||
url = 'https://%s/' % host + ('embed-%s.html' % video_id if host in ('govid.me', 'vidlo.us') else video_id)
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
if any(re.search(p, webpage) for p in self._FILE_NOT_FOUND_REGEXES):
|
||||
@@ -142,7 +100,7 @@ class XFileShareIE(InfoExtractor):
|
||||
|
||||
fields = self._hidden_inputs(webpage)
|
||||
|
||||
if fields['op'] == 'download1':
|
||||
if fields.get('op') == 'download1':
|
||||
countdown = int_or_none(self._search_regex(
|
||||
r'<span id="countdown_str">(?:[Ww]ait)?\s*<span id="cxc">(\d+)</span>\s*(?:seconds?)?</span>',
|
||||
webpage, 'countdown', default=None))
|
||||
@@ -160,13 +118,37 @@ class XFileShareIE(InfoExtractor):
|
||||
(r'style="z-index: [0-9]+;">([^<]+)</span>',
|
||||
r'<td nowrap>([^<]+)</td>',
|
||||
r'h4-fine[^>]*>([^<]+)<',
|
||||
r'>Watch (.+) ',
|
||||
r'>Watch (.+)[ <]',
|
||||
r'<h2 class="video-page-head">([^<]+)</h2>',
|
||||
r'<h2 style="[^"]*color:#403f3d[^"]*"[^>]*>([^<]+)<'), # streamin.to
|
||||
r'<h2 style="[^"]*color:#403f3d[^"]*"[^>]*>([^<]+)<', # streamin.to
|
||||
r'title\s*:\s*"([^"]+)"'), # govid.me
|
||||
webpage, 'title', default=None) or self._og_search_title(
|
||||
webpage, default=None) or video_id).strip()
|
||||
|
||||
def extract_formats(default=NO_DEFAULT):
|
||||
for regex, func in (
|
||||
(r'(eval\(function\(p,a,c,k,e,d\){.+)', decode_packed_codes),
|
||||
(r'(゚.+)', aa_decode)):
|
||||
obf_code = self._search_regex(regex, webpage, 'obfuscated code', default=None)
|
||||
if obf_code:
|
||||
webpage = webpage.replace(obf_code, func(obf_code))
|
||||
|
||||
formats = []
|
||||
|
||||
jwplayer_data = self._search_regex(
|
||||
[
|
||||
r'jwplayer\("[^"]+"\)\.load\(\[({.+?})\]\);',
|
||||
r'jwplayer\("[^"]+"\)\.setup\(({.+?})\);',
|
||||
], webpage,
|
||||
'jwplayer data', default=None)
|
||||
if jwplayer_data:
|
||||
jwplayer_data = self._parse_json(
|
||||
jwplayer_data.replace(r"\'", "'"), video_id, js_to_json)
|
||||
if jwplayer_data:
|
||||
formats = self._parse_jwplayer_data(
|
||||
jwplayer_data, video_id, False,
|
||||
m3u8_id='hls', mpd_id='dash')['formats']
|
||||
|
||||
if not formats:
|
||||
urls = []
|
||||
for regex in (
|
||||
r'(?:file|src)\s*:\s*(["\'])(?P<url>http(?:(?!\1).)+\.(?:m3u8|mp4|flv)(?:(?!\1).)*)\1',
|
||||
@@ -177,6 +159,12 @@ class XFileShareIE(InfoExtractor):
|
||||
video_url = mobj.group('url')
|
||||
if video_url not in urls:
|
||||
urls.append(video_url)
|
||||
|
||||
sources = self._search_regex(
|
||||
r'sources\s*:\s*(\[(?!{)[^\]]+\])', webpage, 'sources', default=None)
|
||||
if sources:
|
||||
urls.extend(self._parse_json(sources, video_id))
|
||||
|
||||
formats = []
|
||||
for video_url in urls:
|
||||
if determine_ext(video_url) == 'm3u8':
|
||||
@@ -189,21 +177,13 @@ class XFileShareIE(InfoExtractor):
|
||||
'url': video_url,
|
||||
'format_id': 'sd',
|
||||
})
|
||||
if not formats and default is not NO_DEFAULT:
|
||||
return default
|
||||
self._sort_formats(formats)
|
||||
return formats
|
||||
|
||||
formats = extract_formats(default=None)
|
||||
|
||||
if not formats:
|
||||
webpage = decode_packed_codes(self._search_regex(
|
||||
r"(}\('(.+)',(\d+),(\d+),'[^']*\b(?:file|embed)\b[^']*'\.split\('\|'\))",
|
||||
webpage, 'packed code'))
|
||||
formats = extract_formats()
|
||||
self._sort_formats(formats)
|
||||
|
||||
thumbnail = self._search_regex(
|
||||
r'image\s*:\s*["\'](http[^"\']+)["\'],', webpage, 'thumbnail', default=None)
|
||||
[
|
||||
r'<video[^>]+poster="([^"]+)"',
|
||||
r'(?:image|poster)\s*:\s*["\'](http[^"\']+)["\'],',
|
||||
], webpage, 'thumbnail', default=None)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
|
@@ -17,7 +17,8 @@ class XVideosIE(InfoExtractor):
|
||||
_VALID_URL = r'''(?x)
|
||||
https?://
|
||||
(?:
|
||||
(?:www\.)?xvideos\.com/video|
|
||||
(?:[^/]+\.)?xvideos2?\.com/video|
|
||||
(?:www\.)?xvideos\.es/video|
|
||||
flashservice\.xvideos\.com/embedframe/|
|
||||
static-hw\.xvideos\.com/swf/xv-player\.swf\?.*?\bid_video=
|
||||
)
|
||||
@@ -39,6 +40,42 @@ class XVideosIE(InfoExtractor):
|
||||
}, {
|
||||
'url': 'http://static-hw.xvideos.com/swf/xv-player.swf?id_video=4588838',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://xvideos.com/video4588838/biker_takes_his_girl',
|
||||
'only_matching': True
|
||||
}, {
|
||||
'url': 'https://xvideos.com/video4588838/biker_takes_his_girl',
|
||||
'only_matching': True
|
||||
}, {
|
||||
'url': 'https://xvideos.es/video4588838/biker_takes_his_girl',
|
||||
'only_matching': True
|
||||
}, {
|
||||
'url': 'https://www.xvideos.es/video4588838/biker_takes_his_girl',
|
||||
'only_matching': True
|
||||
}, {
|
||||
'url': 'http://xvideos.es/video4588838/biker_takes_his_girl',
|
||||
'only_matching': True
|
||||
}, {
|
||||
'url': 'http://www.xvideos.es/video4588838/biker_takes_his_girl',
|
||||
'only_matching': True
|
||||
}, {
|
||||
'url': 'http://fr.xvideos.com/video4588838/biker_takes_his_girl',
|
||||
'only_matching': True
|
||||
}, {
|
||||
'url': 'https://fr.xvideos.com/video4588838/biker_takes_his_girl',
|
||||
'only_matching': True
|
||||
}, {
|
||||
'url': 'http://it.xvideos.com/video4588838/biker_takes_his_girl',
|
||||
'only_matching': True
|
||||
}, {
|
||||
'url': 'https://it.xvideos.com/video4588838/biker_takes_his_girl',
|
||||
'only_matching': True
|
||||
}, {
|
||||
'url': 'http://de.xvideos.com/video4588838/biker_takes_his_girl',
|
||||
'only_matching': True
|
||||
}, {
|
||||
'url': 'https://de.xvideos.com/video4588838/biker_takes_his_girl',
|
||||
'only_matching': True
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
@@ -383,19 +383,21 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
(?:(?:www|no)\.)?invidiou\.sh/|
|
||||
(?:(?:www|fi|de)\.)?invidious\.snopyta\.org/|
|
||||
(?:www\.)?invidious\.kabi\.tk/|
|
||||
(?:www\.)?invidious\.enkirton\.net/|
|
||||
(?:www\.)?invidious\.13ad\.de/|
|
||||
(?:www\.)?invidious\.mastodon\.host/|
|
||||
(?:www\.)?invidious\.nixnet\.xyz/|
|
||||
(?:www\.)?invidious\.drycat\.fr/|
|
||||
(?:www\.)?tube\.poal\.co/|
|
||||
(?:www\.)?vid\.wxzm\.sx/|
|
||||
(?:www\.)?yt\.elukerio\.org/|
|
||||
(?:www\.)?yt\.lelux\.fi/|
|
||||
(?:www\.)?kgg2m7yk5aybusll\.onion/|
|
||||
(?:www\.)?qklhadlycap4cnod\.onion/|
|
||||
(?:www\.)?axqzx4s6s54s32yentfqojs3x5i7faxza6xo3ehd4bzzsg2ii4fv2iid\.onion/|
|
||||
(?:www\.)?c7hqkpkpemu6e7emz5b4vyz7idjgdvgaaa3dyimmeojqbgpea3xqjoid\.onion/|
|
||||
(?:www\.)?fz253lmuao3strwbfbmx46yu7acac2jz27iwtorgmbqlkurlclmancad\.onion/|
|
||||
(?:www\.)?invidious\.l4qlywnpwqsluw65ts7md3khrivpirse744un3x7mlskqauz5pyuzgqd\.onion/|
|
||||
(?:www\.)?owxfohz4kjyv25fvlqilyxast7inivgiktls3th44jhk3ej3i7ya\.b32\.i2p/|
|
||||
youtube\.googleapis\.com/) # the various hostnames, with wildcard subdomains
|
||||
(?:.*?\#/)? # handle anchor (#/) redirect urls
|
||||
(?: # the various things that can precede the ID:
|
||||
@@ -2740,7 +2742,7 @@ class YoutubePlaylistIE(YoutubePlaylistBaseInfoExtractor):
|
||||
page, 'title', default=None)
|
||||
|
||||
_UPLOADER_BASE = r'class=["\']pl-header-details[^>]+>\s*<li>\s*<a[^>]+\bhref='
|
||||
uploader = self._search_regex(
|
||||
uploader = self._html_search_regex(
|
||||
r'%s["\']/(?:user|channel)/[^>]+>([^<]+)' % _UPLOADER_BASE,
|
||||
page, 'uploader', default=None)
|
||||
mobj = re.search(
|
||||
|
@@ -41,6 +41,7 @@ class ZDFBaseIE(InfoExtractor):
|
||||
class ZDFIE(ZDFBaseIE):
|
||||
_VALID_URL = r'https?://www\.zdf\.de/(?:[^/]+/)*(?P<id>[^/?]+)\.html'
|
||||
_QUALITIES = ('auto', 'low', 'med', 'high', 'veryhigh')
|
||||
_GEO_COUNTRIES = ['DE']
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'https://www.zdf.de/dokumentation/terra-x/die-magie-der-farben-von-koenigspurpur-und-jeansblau-100.html',
|
||||
|
@@ -393,7 +393,7 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor):
|
||||
sub_ext = sub_info['ext']
|
||||
if ext != 'webm' or ext == 'webm' and sub_ext == 'vtt':
|
||||
sub_langs.append(lang)
|
||||
sub_filenames.append(subtitles_filename(filename, lang, sub_ext))
|
||||
sub_filenames.append(subtitles_filename(filename, lang, sub_ext, ext))
|
||||
else:
|
||||
if not webm_vtt_warn and ext == 'webm' and sub_ext != 'vtt':
|
||||
webm_vtt_warn = True
|
||||
@@ -606,9 +606,9 @@ class FFmpegSubtitlesConvertorPP(FFmpegPostProcessor):
|
||||
self._downloader.to_screen(
|
||||
'[ffmpeg] Subtitle file for %s is already in the requested format' % new_ext)
|
||||
continue
|
||||
old_file = subtitles_filename(filename, lang, ext)
|
||||
old_file = subtitles_filename(filename, lang, ext, info.get('ext'))
|
||||
sub_filenames.append(old_file)
|
||||
new_file = subtitles_filename(filename, lang, new_ext)
|
||||
new_file = subtitles_filename(filename, lang, new_ext, info.get('ext'))
|
||||
|
||||
if ext in ('dfxp', 'ttml', 'tt'):
|
||||
self._downloader.report_warning(
|
||||
@@ -616,7 +616,7 @@ class FFmpegSubtitlesConvertorPP(FFmpegPostProcessor):
|
||||
'which results in style information loss')
|
||||
|
||||
dfxp_file = old_file
|
||||
srt_file = subtitles_filename(filename, lang, 'srt')
|
||||
srt_file = subtitles_filename(filename, lang, 'srt', info.get('ext'))
|
||||
|
||||
with open(dfxp_file, 'rb') as f:
|
||||
srt_data = dfxp2srt(f.read())
|
||||
|
@@ -2906,8 +2906,8 @@ def determine_ext(url, default_ext='unknown_video'):
|
||||
return default_ext
|
||||
|
||||
|
||||
def subtitles_filename(filename, sub_lang, sub_format):
|
||||
return filename.rsplit('.', 1)[0] + '.' + sub_lang + '.' + sub_format
|
||||
def subtitles_filename(filename, sub_lang, sub_format, expected_real_ext=None):
|
||||
return replace_extension(filename, sub_lang + '.' + sub_format, expected_real_ext)
|
||||
|
||||
|
||||
def date_from_str(date_str):
|
||||
|
@@ -1,3 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__version__ = '2019.09.12.1'
|
||||
__version__ = '2019.10.22'
|
||||
|
Reference in New Issue
Block a user