commit
82c23467f7
6
.github/ISSUE_TEMPLATE.md
vendored
6
.github/ISSUE_TEMPLATE.md
vendored
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Make sure you are using the *latest* version: run `youtube-dl --version` and ensure your version is *2016.10.19*. If it's not read [this FAQ entry](https://github.com/rg3/youtube-dl/blob/master/README.md#how-do-i-update-youtube-dl) and update. Issues with outdated version will be rejected.
|
### Make sure you are using the *latest* version: run `youtube-dl --version` and ensure your version is *2016.10.21.1*. If it's not read [this FAQ entry](https://github.com/rg3/youtube-dl/blob/master/README.md#how-do-i-update-youtube-dl) and update. Issues with outdated version will be rejected.
|
||||||
- [ ] I've **verified** and **I assure** that I'm running youtube-dl **2016.10.19**
|
- [ ] I've **verified** and **I assure** that I'm running youtube-dl **2016.10.21.1**
|
||||||
|
|
||||||
### Before submitting an *issue* make sure you have:
|
### Before submitting an *issue* make sure you have:
|
||||||
- [ ] At least skimmed through [README](https://github.com/rg3/youtube-dl/blob/master/README.md) and **most notably** [FAQ](https://github.com/rg3/youtube-dl#faq) and [BUGS](https://github.com/rg3/youtube-dl#bugs) sections
|
- [ ] At least skimmed through [README](https://github.com/rg3/youtube-dl/blob/master/README.md) and **most notably** [FAQ](https://github.com/rg3/youtube-dl#faq) and [BUGS](https://github.com/rg3/youtube-dl#bugs) sections
|
||||||
@ -35,7 +35,7 @@ $ youtube-dl -v <your command line>
|
|||||||
[debug] User config: []
|
[debug] User config: []
|
||||||
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
||||||
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
||||||
[debug] youtube-dl version 2016.10.19
|
[debug] youtube-dl version 2016.10.21.1
|
||||||
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
||||||
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
||||||
[debug] Proxy map: {}
|
[debug] Proxy map: {}
|
||||||
|
22
ChangeLog
22
ChangeLog
@ -1,7 +1,29 @@
|
|||||||
version <unreleased>
|
version <unreleased>
|
||||||
|
|
||||||
|
Core
|
||||||
|
* Running youtube-dl in the background is fixed (#10996, #10706, #955)
|
||||||
|
|
||||||
Extractors
|
Extractors
|
||||||
|
+ [dotsub] Support Vimeo embed (#10964)
|
||||||
|
* [litv] Fix extraction
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.10.21.1
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [pluralsight] Process all clip URLs (#10984)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.10.21
|
||||||
|
|
||||||
|
Core
|
||||||
|
- Disable thumbnails embedding in mkv
|
||||||
|
+ Add support for Comcast multiple-system operator (#10819)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [pluralsight] Adapt to new API (#10972)
|
||||||
* [openload] Fix extraction (#10408, #10971)
|
* [openload] Fix extraction (#10408, #10971)
|
||||||
|
+ [natgeo] Extract m3u8 formats (#10959)
|
||||||
|
|
||||||
|
|
||||||
version 2016.10.19
|
version 2016.10.19
|
||||||
|
@ -1376,19 +1376,23 @@ class AdobePassIE(InfoExtractor):
|
|||||||
provider_redirect_page, urlh = provider_redirect_page_res
|
provider_redirect_page, urlh = provider_redirect_page_res
|
||||||
# Check for Comcast auto login
|
# Check for Comcast auto login
|
||||||
if 'automatically signing you in' in provider_redirect_page:
|
if 'automatically signing you in' in provider_redirect_page:
|
||||||
oauth_redirect_url = self._html_search_regex(r'window\.location\s*=\s*[\'"]([^\'"]+)',
|
oauth_redirect_url = self._html_search_regex(
|
||||||
|
r'window\.location\s*=\s*[\'"]([^\'"]+)',
|
||||||
provider_redirect_page, 'oauth redirect')
|
provider_redirect_page, 'oauth redirect')
|
||||||
# Just need to process the request. No useful data comes back
|
# Just need to process the request. No useful data comes back
|
||||||
self._download_webpage(oauth_redirect_url, video_id, 'Confirming auto login')
|
self._download_webpage(
|
||||||
|
oauth_redirect_url, video_id, 'Confirming auto login')
|
||||||
else:
|
else:
|
||||||
if '<form name="signin"' in provider_redirect_page:
|
if '<form name="signin"' in provider_redirect_page:
|
||||||
# already have the form, just fill it
|
# already have the form, just fill it
|
||||||
provider_login_page_res = provider_redirect_page_res
|
provider_login_page_res = provider_redirect_page_res
|
||||||
elif 'http-equiv="refresh"' in provider_redirect_page:
|
elif 'http-equiv="refresh"' in provider_redirect_page:
|
||||||
# redirects to the login page
|
# redirects to the login page
|
||||||
oauth_redirect_url = self._html_search_regex(r'content="0;\s*url=([^\'"]+)',
|
oauth_redirect_url = self._html_search_regex(
|
||||||
|
r'content="0;\s*url=([^\'"]+)',
|
||||||
provider_redirect_page, 'meta refresh redirect')
|
provider_redirect_page, 'meta refresh redirect')
|
||||||
provider_login_page_res = self._download_webpage_handle(oauth_redirect_url,
|
provider_login_page_res = self._download_webpage_handle(
|
||||||
|
oauth_redirect_url,
|
||||||
video_id, 'Downloading Provider Login Page')
|
video_id, 'Downloading Provider Login Page')
|
||||||
else:
|
else:
|
||||||
provider_login_page_res = post_form(
|
provider_login_page_res = post_form(
|
||||||
|
@ -9,7 +9,7 @@ from ..utils import (
|
|||||||
|
|
||||||
class DotsubIE(InfoExtractor):
|
class DotsubIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?dotsub\.com/view/(?P<id>[^/]+)'
|
_VALID_URL = r'https?://(?:www\.)?dotsub\.com/view/(?P<id>[^/]+)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'https://dotsub.com/view/9c63db2a-fa95-4838-8e6e-13deafe47f09',
|
'url': 'https://dotsub.com/view/9c63db2a-fa95-4838-8e6e-13deafe47f09',
|
||||||
'md5': '21c7ff600f545358134fea762a6d42b6',
|
'md5': '21c7ff600f545358134fea762a6d42b6',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -24,7 +24,24 @@ class DotsubIE(InfoExtractor):
|
|||||||
'upload_date': '20131130',
|
'upload_date': '20131130',
|
||||||
'view_count': int,
|
'view_count': int,
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'https://dotsub.com/view/747bcf58-bd59-45b7-8c8c-ac312d084ee6',
|
||||||
|
'md5': '2bb4a83896434d5c26be868c609429a3',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '168006778',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Apartments and flats in Raipur the white symphony',
|
||||||
|
'description': 'md5:784d0639e6b7d1bc29530878508e38fe',
|
||||||
|
'thumbnail': 're:^https?://dotsub.com/media/747bcf58-bd59-45b7-8c8c-ac312d084ee6/p',
|
||||||
|
'duration': 290,
|
||||||
|
'timestamp': 1476767794.2809999,
|
||||||
|
'upload_date': '20160525',
|
||||||
|
'uploader': 'parthivi001',
|
||||||
|
'uploader_id': 'user52596202',
|
||||||
|
'view_count': int,
|
||||||
|
},
|
||||||
|
'add_ie': ['Vimeo'],
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
@ -37,12 +54,23 @@ class DotsubIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
video_url = self._search_regex(
|
video_url = self._search_regex(
|
||||||
[r'<source[^>]+src="([^"]+)"', r'"file"\s*:\s*\'([^\']+)'],
|
[r'<source[^>]+src="([^"]+)"', r'"file"\s*:\s*\'([^\']+)'],
|
||||||
webpage, 'video url')
|
webpage, 'video url', default=None)
|
||||||
|
info_dict = {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'ext': 'flv',
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
if not video_url:
|
||||||
'id': video_id,
|
setup_data = self._parse_json(self._html_search_regex(
|
||||||
'url': video_url,
|
r'(?s)data-setup=([\'"])(?P<content>(?!\1).+?)\1',
|
||||||
'ext': 'flv',
|
webpage, 'setup data', group='content'), video_id)
|
||||||
|
info_dict = {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': setup_data['src'],
|
||||||
|
}
|
||||||
|
|
||||||
|
info_dict.update({
|
||||||
'title': info['title'],
|
'title': info['title'],
|
||||||
'description': info.get('description'),
|
'description': info.get('description'),
|
||||||
'thumbnail': info.get('screenshotURI'),
|
'thumbnail': info.get('screenshotURI'),
|
||||||
@ -50,4 +78,6 @@ class DotsubIE(InfoExtractor):
|
|||||||
'uploader': info.get('user'),
|
'uploader': info.get('user'),
|
||||||
'timestamp': float_or_none(info.get('dateCreated'), 1000),
|
'timestamp': float_or_none(info.get('dateCreated'), 1000),
|
||||||
'view_count': int_or_none(info.get('numberOfViews')),
|
'view_count': int_or_none(info.get('numberOfViews')),
|
||||||
}
|
})
|
||||||
|
|
||||||
|
return info_dict
|
||||||
|
@ -796,7 +796,10 @@ from .sendtonews import SendtoNewsIE
|
|||||||
from .servingsys import ServingSysIE
|
from .servingsys import ServingSysIE
|
||||||
from .sexu import SexuIE
|
from .sexu import SexuIE
|
||||||
from .shahid import ShahidIE
|
from .shahid import ShahidIE
|
||||||
from .shared import SharedIE
|
from .shared import (
|
||||||
|
SharedIE,
|
||||||
|
VivoIE,
|
||||||
|
)
|
||||||
from .sharesix import ShareSixIE
|
from .sharesix import ShareSixIE
|
||||||
from .sina import SinaIE
|
from .sina import SinaIE
|
||||||
from .sixplay import SixPlayIE
|
from .sixplay import SixPlayIE
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@ -52,8 +51,8 @@ class LiTVIE(InfoExtractor):
|
|||||||
'skip': 'Georestricted to Taiwan',
|
'skip': 'Georestricted to Taiwan',
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _extract_playlist(self, season_list, video_id, vod_data, view_data, prompt=True):
|
def _extract_playlist(self, season_list, video_id, program_info, prompt=True):
|
||||||
episode_title = view_data['title']
|
episode_title = program_info['title']
|
||||||
content_id = season_list['contentId']
|
content_id = season_list['contentId']
|
||||||
|
|
||||||
if prompt:
|
if prompt:
|
||||||
@ -61,7 +60,7 @@ class LiTVIE(InfoExtractor):
|
|||||||
|
|
||||||
all_episodes = [
|
all_episodes = [
|
||||||
self.url_result(smuggle_url(
|
self.url_result(smuggle_url(
|
||||||
self._URL_TEMPLATE % (view_data['contentType'], episode['contentId']),
|
self._URL_TEMPLATE % (program_info['contentType'], episode['contentId']),
|
||||||
{'force_noplaylist': True})) # To prevent infinite recursion
|
{'force_noplaylist': True})) # To prevent infinite recursion
|
||||||
for episode in season_list['episode']]
|
for episode in season_list['episode']]
|
||||||
|
|
||||||
@ -80,19 +79,15 @@ class LiTVIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
view_data = dict(map(lambda t: (t[0], t[2]), re.findall(
|
program_info = self._parse_json(self._search_regex(
|
||||||
r'viewData\.([a-zA-Z]+)\s*=\s*(["\'])([^"\']+)\2',
|
'var\s+programInfo\s*=\s*([^;]+)', webpage, 'VOD data', default='{}'),
|
||||||
webpage)))
|
|
||||||
|
|
||||||
vod_data = self._parse_json(self._search_regex(
|
|
||||||
'var\s+vod\s*=\s*([^;]+)', webpage, 'VOD data', default='{}'),
|
|
||||||
video_id)
|
video_id)
|
||||||
|
|
||||||
season_list = list(vod_data.get('seasonList', {}).values())
|
season_list = list(program_info.get('seasonList', {}).values())
|
||||||
if season_list:
|
if season_list:
|
||||||
if not noplaylist:
|
if not noplaylist:
|
||||||
return self._extract_playlist(
|
return self._extract_playlist(
|
||||||
season_list[0], video_id, vod_data, view_data,
|
season_list[0], video_id, program_info,
|
||||||
prompt=noplaylist_prompt)
|
prompt=noplaylist_prompt)
|
||||||
|
|
||||||
if noplaylist_prompt:
|
if noplaylist_prompt:
|
||||||
@ -102,8 +97,8 @@ class LiTVIE(InfoExtractor):
|
|||||||
# endpoint gives the same result as the data embedded in the webpage.
|
# endpoint gives the same result as the data embedded in the webpage.
|
||||||
# If georestricted, there are no embedded data, so an extra request is
|
# If georestricted, there are no embedded data, so an extra request is
|
||||||
# necessary to get the error code
|
# necessary to get the error code
|
||||||
if 'assetId' not in view_data:
|
if 'assetId' not in program_info:
|
||||||
view_data = self._download_json(
|
program_info = self._download_json(
|
||||||
'https://www.litv.tv/vod/ajax/getProgramInfo', video_id,
|
'https://www.litv.tv/vod/ajax/getProgramInfo', video_id,
|
||||||
query={'contentId': video_id},
|
query={'contentId': video_id},
|
||||||
headers={'Accept': 'application/json'})
|
headers={'Accept': 'application/json'})
|
||||||
@ -112,9 +107,9 @@ class LiTVIE(InfoExtractor):
|
|||||||
webpage, 'video data', default='{}'), video_id)
|
webpage, 'video data', default='{}'), video_id)
|
||||||
if not video_data:
|
if not video_data:
|
||||||
payload = {
|
payload = {
|
||||||
'assetId': view_data['assetId'],
|
'assetId': program_info['assetId'],
|
||||||
'watchDevices': view_data['watchDevices'],
|
'watchDevices': program_info['watchDevices'],
|
||||||
'contentType': view_data['contentType'],
|
'contentType': program_info['contentType'],
|
||||||
}
|
}
|
||||||
video_data = self._download_json(
|
video_data = self._download_json(
|
||||||
'https://www.litv.tv/vod/getMainUrl', video_id,
|
'https://www.litv.tv/vod/getMainUrl', video_id,
|
||||||
@ -136,11 +131,11 @@ class LiTVIE(InfoExtractor):
|
|||||||
# LiTV HLS segments doesn't like compressions
|
# LiTV HLS segments doesn't like compressions
|
||||||
a_format.setdefault('http_headers', {})['Youtubedl-no-compression'] = True
|
a_format.setdefault('http_headers', {})['Youtubedl-no-compression'] = True
|
||||||
|
|
||||||
title = view_data['title'] + view_data.get('secondaryMark', '')
|
title = program_info['title'] + program_info.get('secondaryMark', '')
|
||||||
description = view_data.get('description')
|
description = program_info.get('description')
|
||||||
thumbnail = view_data.get('imageFile')
|
thumbnail = program_info.get('imageFile')
|
||||||
categories = [item['name'] for item in vod_data.get('category', [])]
|
categories = [item['name'] for item in program_info.get('category', [])]
|
||||||
episode = int_or_none(view_data.get('episode'))
|
episode = int_or_none(program_info.get('episode'))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
@ -4,7 +4,6 @@ import collections
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
@ -12,6 +11,7 @@ from ..compat import (
|
|||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
dict_get,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
@ -23,7 +23,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class PluralsightBaseIE(InfoExtractor):
|
class PluralsightBaseIE(InfoExtractor):
|
||||||
_API_BASE = 'http://app.pluralsight.com'
|
_API_BASE = 'https://app.pluralsight.com'
|
||||||
|
|
||||||
|
|
||||||
class PluralsightIE(PluralsightBaseIE):
|
class PluralsightIE(PluralsightBaseIE):
|
||||||
@ -102,7 +102,7 @@ class PluralsightIE(PluralsightBaseIE):
|
|||||||
'm': name,
|
'm': name,
|
||||||
}
|
}
|
||||||
captions = self._download_json(
|
captions = self._download_json(
|
||||||
'%s/training/Player/Captions' % self._API_BASE, video_id,
|
'%s/player/retrieve-captions' % self._API_BASE, video_id,
|
||||||
'Downloading captions JSON', 'Unable to download captions JSON',
|
'Downloading captions JSON', 'Unable to download captions JSON',
|
||||||
fatal=False, data=json.dumps(captions_post).encode('utf-8'),
|
fatal=False, data=json.dumps(captions_post).encode('utf-8'),
|
||||||
headers={'Content-Type': 'application/json;charset=utf-8'})
|
headers={'Content-Type': 'application/json;charset=utf-8'})
|
||||||
@ -120,14 +120,17 @@ class PluralsightIE(PluralsightBaseIE):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _convert_subtitles(duration, subs):
|
def _convert_subtitles(duration, subs):
|
||||||
srt = ''
|
srt = ''
|
||||||
|
TIME_OFFSET_KEYS = ('displayTimeOffset', 'DisplayTimeOffset')
|
||||||
|
TEXT_KEYS = ('text', 'Text')
|
||||||
for num, current in enumerate(subs):
|
for num, current in enumerate(subs):
|
||||||
current = subs[num]
|
current = subs[num]
|
||||||
start, text = float_or_none(
|
start, text = (
|
||||||
current.get('DisplayTimeOffset')), current.get('Text')
|
float_or_none(dict_get(current, TIME_OFFSET_KEYS)),
|
||||||
|
dict_get(current, TEXT_KEYS))
|
||||||
if start is None or text is None:
|
if start is None or text is None:
|
||||||
continue
|
continue
|
||||||
end = duration if num == len(subs) - 1 else float_or_none(
|
end = duration if num == len(subs) - 1 else float_or_none(
|
||||||
subs[num + 1].get('DisplayTimeOffset'))
|
dict_get(subs[num + 1], TIME_OFFSET_KEYS))
|
||||||
if end is None:
|
if end is None:
|
||||||
continue
|
continue
|
||||||
srt += os.linesep.join(
|
srt += os.linesep.join(
|
||||||
@ -147,28 +150,22 @@ class PluralsightIE(PluralsightBaseIE):
|
|||||||
author = qs.get('author', [None])[0]
|
author = qs.get('author', [None])[0]
|
||||||
name = qs.get('name', [None])[0]
|
name = qs.get('name', [None])[0]
|
||||||
clip_id = qs.get('clip', [None])[0]
|
clip_id = qs.get('clip', [None])[0]
|
||||||
course = qs.get('course', [None])[0]
|
course_name = qs.get('course', [None])[0]
|
||||||
|
|
||||||
if any(not f for f in (author, name, clip_id, course,)):
|
if any(not f for f in (author, name, clip_id, course_name,)):
|
||||||
raise ExtractorError('Invalid URL', expected=True)
|
raise ExtractorError('Invalid URL', expected=True)
|
||||||
|
|
||||||
display_id = '%s-%s' % (name, clip_id)
|
display_id = '%s-%s' % (name, clip_id)
|
||||||
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
parsed_url = compat_urlparse.urlparse(url)
|
||||||
|
|
||||||
modules = self._search_regex(
|
payload_url = compat_urlparse.urlunparse(parsed_url._replace(
|
||||||
r'moduleCollection\s*:\s*new\s+ModuleCollection\((\[.+?\])\s*,\s*\$rootScope\)',
|
netloc='app.pluralsight.com', path='player/api/v1/payload'))
|
||||||
webpage, 'modules', default=None)
|
|
||||||
|
|
||||||
if modules:
|
course = self._download_json(
|
||||||
collection = self._parse_json(modules, display_id)
|
payload_url, display_id, headers={'Referer': url})['payload']['course']
|
||||||
else:
|
|
||||||
# Webpage may be served in different layout (see
|
collection = course['modules']
|
||||||
# https://github.com/rg3/youtube-dl/issues/7607)
|
|
||||||
collection = self._parse_json(
|
|
||||||
self._search_regex(
|
|
||||||
r'var\s+initialState\s*=\s*({.+?});\n', webpage, 'initial state'),
|
|
||||||
display_id)['course']['modules']
|
|
||||||
|
|
||||||
module, clip = None, None
|
module, clip = None, None
|
||||||
|
|
||||||
@ -209,8 +206,7 @@ class PluralsightIE(PluralsightBaseIE):
|
|||||||
|
|
||||||
# Some courses also offer widescreen resolution for high quality (see
|
# Some courses also offer widescreen resolution for high quality (see
|
||||||
# https://github.com/rg3/youtube-dl/issues/7766)
|
# https://github.com/rg3/youtube-dl/issues/7766)
|
||||||
widescreen = True if re.search(
|
widescreen = course.get('supportsWideScreenVideoFormats') is True
|
||||||
r'courseSupportsWidescreenVideoFormats\s*:\s*true', webpage) else False
|
|
||||||
best_quality = 'high-widescreen' if widescreen else 'high'
|
best_quality = 'high-widescreen' if widescreen else 'high'
|
||||||
if widescreen:
|
if widescreen:
|
||||||
for allowed_quality in ALLOWED_QUALITIES:
|
for allowed_quality in ALLOWED_QUALITIES:
|
||||||
@ -239,19 +235,19 @@ class PluralsightIE(PluralsightBaseIE):
|
|||||||
for quality in qualities_:
|
for quality in qualities_:
|
||||||
f = QUALITIES[quality].copy()
|
f = QUALITIES[quality].copy()
|
||||||
clip_post = {
|
clip_post = {
|
||||||
'a': author,
|
'author': author,
|
||||||
'cap': 'false',
|
'includeCaptions': False,
|
||||||
'cn': clip_id,
|
'clipIndex': int(clip_id),
|
||||||
'course': course,
|
'courseName': course_name,
|
||||||
'lc': 'en',
|
'locale': 'en',
|
||||||
'm': name,
|
'moduleName': name,
|
||||||
'mt': ext,
|
'mediaType': ext,
|
||||||
'q': '%dx%d' % (f['width'], f['height']),
|
'quality': '%dx%d' % (f['width'], f['height']),
|
||||||
}
|
}
|
||||||
format_id = '%s-%s' % (ext, quality)
|
format_id = '%s-%s' % (ext, quality)
|
||||||
clip_url = self._download_webpage(
|
viewclip = self._download_json(
|
||||||
'%s/training/Player/ViewClip' % self._API_BASE, display_id,
|
'%s/video/clips/viewclip' % self._API_BASE, display_id,
|
||||||
'Downloading %s URL' % format_id, fatal=False,
|
'Downloading %s viewclip JSON' % format_id, fatal=False,
|
||||||
data=json.dumps(clip_post).encode('utf-8'),
|
data=json.dumps(clip_post).encode('utf-8'),
|
||||||
headers={'Content-Type': 'application/json;charset=utf-8'})
|
headers={'Content-Type': 'application/json;charset=utf-8'})
|
||||||
|
|
||||||
@ -265,15 +261,28 @@ class PluralsightIE(PluralsightBaseIE):
|
|||||||
random.randint(2, 5), display_id,
|
random.randint(2, 5), display_id,
|
||||||
'%(video_id)s: Waiting for %(timeout)s seconds to avoid throttling')
|
'%(video_id)s: Waiting for %(timeout)s seconds to avoid throttling')
|
||||||
|
|
||||||
if not clip_url:
|
if not viewclip:
|
||||||
continue
|
continue
|
||||||
f.update({
|
|
||||||
'url': clip_url,
|
clip_urls = viewclip.get('urls')
|
||||||
'ext': ext,
|
if not isinstance(clip_urls, list):
|
||||||
'format_id': format_id,
|
continue
|
||||||
'quality': quality_key(quality),
|
|
||||||
})
|
for clip_url_data in clip_urls:
|
||||||
formats.append(f)
|
clip_url = clip_url_data.get('url')
|
||||||
|
if not clip_url:
|
||||||
|
continue
|
||||||
|
cdn = clip_url_data.get('cdn')
|
||||||
|
clip_f = f.copy()
|
||||||
|
clip_f.update({
|
||||||
|
'url': clip_url,
|
||||||
|
'ext': ext,
|
||||||
|
'format_id': '%s-%s' % (format_id, cdn) if cdn else format_id,
|
||||||
|
'quality': quality_key(quality),
|
||||||
|
'source_preference': int_or_none(clip_url_data.get('rank')),
|
||||||
|
})
|
||||||
|
formats.append(clip_f)
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
duration = int_or_none(
|
duration = int_or_none(
|
||||||
|
@ -10,11 +10,38 @@ from ..utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SharedIE(InfoExtractor):
|
class SharedBaseIE(InfoExtractor):
|
||||||
IE_DESC = 'shared.sx and vivo.sx'
|
def _real_extract(self, url):
|
||||||
_VALID_URL = r'https?://(?:shared|vivo)\.sx/(?P<id>[\da-z]{10})'
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
_TESTS = [{
|
webpage, urlh = self._download_webpage_handle(url, video_id)
|
||||||
|
|
||||||
|
if self._FILE_NOT_FOUND in webpage:
|
||||||
|
raise ExtractorError(
|
||||||
|
'Video %s does not exist' % video_id, expected=True)
|
||||||
|
|
||||||
|
video_url = self._extract_video_url(webpage, video_id, url)
|
||||||
|
|
||||||
|
title = base64.b64decode(self._html_search_meta(
|
||||||
|
'full:title', webpage, 'title').encode('utf-8')).decode('utf-8')
|
||||||
|
filesize = int_or_none(self._html_search_meta(
|
||||||
|
'full:size', webpage, 'file size', fatal=False))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'ext': 'mp4',
|
||||||
|
'filesize': filesize,
|
||||||
|
'title': title,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SharedIE(SharedBaseIE):
|
||||||
|
IE_DESC = 'shared.sx'
|
||||||
|
_VALID_URL = r'https?://shared\.sx/(?P<id>[\da-z]{10})'
|
||||||
|
_FILE_NOT_FOUND = '>File does not exist<'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
'url': 'http://shared.sx/0060718775',
|
'url': 'http://shared.sx/0060718775',
|
||||||
'md5': '106fefed92a8a2adb8c98e6a0652f49b',
|
'md5': '106fefed92a8a2adb8c98e6a0652f49b',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -23,7 +50,32 @@ class SharedIE(InfoExtractor):
|
|||||||
'title': 'Bmp4',
|
'title': 'Bmp4',
|
||||||
'filesize': 1720110,
|
'filesize': 1720110,
|
||||||
},
|
},
|
||||||
}, {
|
}
|
||||||
|
|
||||||
|
def _extract_video_url(self, webpage, video_id, url):
|
||||||
|
download_form = self._hidden_inputs(webpage)
|
||||||
|
|
||||||
|
video_page = self._download_webpage(
|
||||||
|
url, video_id, 'Downloading video page',
|
||||||
|
data=urlencode_postdata(download_form),
|
||||||
|
headers={
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'Referer': url,
|
||||||
|
})
|
||||||
|
|
||||||
|
video_url = self._html_search_regex(
|
||||||
|
r'data-url=(["\'])(?P<url>(?:(?!\1).)+)\1',
|
||||||
|
video_page, 'video URL', group='url')
|
||||||
|
|
||||||
|
return video_url
|
||||||
|
|
||||||
|
|
||||||
|
class VivoIE(SharedBaseIE):
|
||||||
|
IE_DESC = 'vivo.sx'
|
||||||
|
_VALID_URL = r'https?://vivo\.sx/(?P<id>[\da-z]{10})'
|
||||||
|
_FILE_NOT_FOUND = '>The file you have requested does not exists or has been removed'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
'url': 'http://vivo.sx/d7ddda0e78',
|
'url': 'http://vivo.sx/d7ddda0e78',
|
||||||
'md5': '15b3af41be0b4fe01f4df075c2678b2c',
|
'md5': '15b3af41be0b4fe01f4df075c2678b2c',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -32,43 +84,13 @@ class SharedIE(InfoExtractor):
|
|||||||
'title': 'Chicken',
|
'title': 'Chicken',
|
||||||
'filesize': 528031,
|
'filesize': 528031,
|
||||||
},
|
},
|
||||||
}]
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _extract_video_url(self, webpage, video_id, *args):
|
||||||
video_id = self._match_id(url)
|
return self._parse_json(
|
||||||
|
self._search_regex(
|
||||||
webpage, urlh = self._download_webpage_handle(url, video_id)
|
r'InitializeStream\s*\(\s*(["\'])(?P<url>(?:(?!\1).)+)\1',
|
||||||
|
webpage, 'stream', group='url'),
|
||||||
if '>File does not exist<' in webpage:
|
video_id,
|
||||||
raise ExtractorError(
|
transform_source=lambda x: base64.b64decode(
|
||||||
'Video %s does not exist' % video_id, expected=True)
|
x.encode('ascii')).decode('utf-8'))[0]
|
||||||
|
|
||||||
download_form = self._hidden_inputs(webpage)
|
|
||||||
|
|
||||||
video_page = self._download_webpage(
|
|
||||||
urlh.geturl(), video_id, 'Downloading video page',
|
|
||||||
data=urlencode_postdata(download_form),
|
|
||||||
headers={
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'Referer': urlh.geturl(),
|
|
||||||
})
|
|
||||||
|
|
||||||
video_url = self._html_search_regex(
|
|
||||||
r'data-url=(["\'])(?P<url>(?:(?!\1).)+)\1',
|
|
||||||
video_page, 'video URL', group='url')
|
|
||||||
title = base64.b64decode(self._html_search_meta(
|
|
||||||
'full:title', webpage, 'title').encode('utf-8')).decode('utf-8')
|
|
||||||
filesize = int_or_none(self._html_search_meta(
|
|
||||||
'full:size', webpage, 'file size', fatal=False))
|
|
||||||
thumbnail = self._html_search_regex(
|
|
||||||
r'data-poster=(["\'])(?P<url>(?:(?!\1).)+)\1',
|
|
||||||
video_page, 'thumbnail', default=None, group='url')
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'url': video_url,
|
|
||||||
'ext': 'mp4',
|
|
||||||
'filesize': filesize,
|
|
||||||
'title': title,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
}
|
|
||||||
|
@ -398,7 +398,7 @@ class TwitchStreamIE(TwitchBaseIE):
|
|||||||
channel_id = self._match_id(url)
|
channel_id = self._match_id(url)
|
||||||
|
|
||||||
stream = self._call_api(
|
stream = self._call_api(
|
||||||
'kraken/streams/%s' % channel_id, channel_id,
|
'kraken/streams/%s?stream_type=all' % channel_id, channel_id,
|
||||||
'Downloading stream JSON').get('stream')
|
'Downloading stream JSON').get('stream')
|
||||||
|
|
||||||
if not stream:
|
if not stream:
|
||||||
@ -417,6 +417,7 @@ class TwitchStreamIE(TwitchBaseIE):
|
|||||||
query = {
|
query = {
|
||||||
'allow_source': 'true',
|
'allow_source': 'true',
|
||||||
'allow_audio_only': 'true',
|
'allow_audio_only': 'true',
|
||||||
|
'allow_spectre': 'true',
|
||||||
'p': random.randint(1000000, 10000000),
|
'p': random.randint(1000000, 10000000),
|
||||||
'player': 'twitchweb',
|
'player': 'twitchweb',
|
||||||
'segment_preference': '4',
|
'segment_preference': '4',
|
||||||
|
@ -322,6 +322,22 @@ class VimeoIE(VimeoBaseInfoExtractor):
|
|||||||
},
|
},
|
||||||
'expected_warnings': ['Unable to download JSON metadata'],
|
'expected_warnings': ['Unable to download JSON metadata'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
# redirects to ondemand extractor and should be passed throught it
|
||||||
|
# for successful extraction
|
||||||
|
'url': 'https://vimeo.com/73445910',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '73445910',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'The Reluctant Revolutionary',
|
||||||
|
'uploader': '10Ft Films',
|
||||||
|
'uploader_url': 're:https?://(?:www\.)?vimeo\.com/tenfootfilms',
|
||||||
|
'uploader_id': 'tenfootfilms',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://vimeo.com/moogaloop.swf?clip_id=2539741',
|
'url': 'http://vimeo.com/moogaloop.swf?clip_id=2539741',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@ -414,7 +430,12 @@ class VimeoIE(VimeoBaseInfoExtractor):
|
|||||||
# Retrieve video webpage to extract further information
|
# Retrieve video webpage to extract further information
|
||||||
request = sanitized_Request(url, headers=headers)
|
request = sanitized_Request(url, headers=headers)
|
||||||
try:
|
try:
|
||||||
webpage = self._download_webpage(request, video_id)
|
webpage, urlh = self._download_webpage_handle(request, video_id)
|
||||||
|
# Some URLs redirect to ondemand can't be extracted with
|
||||||
|
# this extractor right away thus should be passed through
|
||||||
|
# ondemand extractor (e.g. https://vimeo.com/73445910)
|
||||||
|
if VimeoOndemandIE.suitable(urlh.geturl()):
|
||||||
|
return self.url_result(urlh.geturl(), VimeoOndemandIE.ie_key())
|
||||||
except ExtractorError as ee:
|
except ExtractorError as ee:
|
||||||
if isinstance(ee.cause, compat_HTTPError) and ee.cause.code == 403:
|
if isinstance(ee.cause, compat_HTTPError) and ee.cause.code == 403:
|
||||||
errmsg = ee.cause.read()
|
errmsg = ee.cause.read()
|
||||||
|
@ -1818,8 +1818,12 @@ def get_exe_version(exe, args=['--version'],
|
|||||||
""" Returns the version of the specified executable,
|
""" Returns the version of the specified executable,
|
||||||
or False if the executable is not present """
|
or False if the executable is not present """
|
||||||
try:
|
try:
|
||||||
|
# STDIN should be redirected too. On UNIX-like systems, ffmpeg triggers
|
||||||
|
# SIGTTOU if youtube-dl is run in the background.
|
||||||
|
# See https://github.com/rg3/youtube-dl/issues/955#issuecomment-209789656
|
||||||
out, _ = subprocess.Popen(
|
out, _ = subprocess.Popen(
|
||||||
[encodeArgument(exe)] + args,
|
[encodeArgument(exe)] + args,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()
|
||||||
except OSError:
|
except OSError:
|
||||||
return False
|
return False
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
__version__ = '2016.10.19'
|
__version__ = '2016.10.21.1'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user