Compare commits
28 Commits
2017.10.20
...
2017.10.29
Author | SHA1 | Date | |
---|---|---|---|
|
6d0630d880 | ||
|
518d357b46 | ||
|
514e8aefd4 | ||
|
9211e3319e | ||
|
056653bbb1 | ||
|
c3206d02e9 | ||
|
eb4b5818e2 | ||
|
47a8587915 | ||
|
8e01f3ca81 | ||
|
f2332f18e6 | ||
|
7c1f419341 | ||
|
30e6161799 | ||
|
dc24a7d4a2 | ||
|
d673ab6562 | ||
|
b8c6ffc518 | ||
|
7913e0fca7 | ||
|
cdd1ce92c4 | ||
|
55c727a547 | ||
|
36e2d3ca43 | ||
|
f7a5038305 | ||
|
9ff6273cae | ||
|
f03ee0b372 | ||
|
cf6bda312b | ||
|
3ebbd9991e | ||
|
21ce434051 | ||
|
5c0e5bc4df | ||
|
9a9de2d7b2 | ||
|
424505df76 |
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 *2017.10.20*. 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 **2017.10.20**
|
||||
### Make sure you are using the *latest* version: run `youtube-dl --version` and ensure your version is *2017.10.29*. 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 **2017.10.29**
|
||||
|
||||
### Before submitting an *issue* make sure you have:
|
||||
- [ ] At least skimmed through the [README](https://github.com/rg3/youtube-dl/blob/master/README.md), **most notably** the [FAQ](https://github.com/rg3/youtube-dl#faq) and [BUGS](https://github.com/rg3/youtube-dl#bugs) sections
|
||||
@@ -35,7 +35,7 @@ Add the `-v` flag to **your command line** you run youtube-dl with (`youtube-dl
|
||||
[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 2017.10.20
|
||||
[debug] youtube-dl version 2017.10.29
|
||||
[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: {}
|
||||
|
@@ -11,12 +11,12 @@ sudo: false
|
||||
env:
|
||||
- YTDL_TEST_SET=core
|
||||
- YTDL_TEST_SET=download
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: YTDL_TEST_SET=download
|
||||
script: ./devscripts/run_tests.sh
|
||||
notifications:
|
||||
email:
|
||||
- filippo.valsorda@gmail.com
|
||||
- yasoob.khld@gmail.com
|
||||
# irc:
|
||||
# channels:
|
||||
# - "irc.freenode.org#youtube-dl"
|
||||
# skip_join: true
|
||||
|
25
ChangeLog
25
ChangeLog
@@ -1,3 +1,28 @@
|
||||
version 2017.10.29
|
||||
|
||||
Core
|
||||
* [extractor/common] Prefix format id for audio only HLS formats
|
||||
+ [utils] Add support for zero years and months in parse_duration
|
||||
|
||||
Extractors
|
||||
* [egghead] Fix extraction (#14388)
|
||||
+ [fxnetworks] Extract series metadata (#14603)
|
||||
+ [younow] Add support for younow.com (#9255, #9432, #12436)
|
||||
* [dctptv] Fix extraction (#14599)
|
||||
* [youtube] Restrict embed regex (#14600)
|
||||
* [vimeo] Restrict iframe embed regex (#14600)
|
||||
* [soundgasm] Improve extraction (#14588)
|
||||
- [myvideo] Remove extractor (#8557)
|
||||
+ [nbc] Add support for classic-tv videos (#14575)
|
||||
+ [vrtnu] Add support for cookies authentication and simplify (#11873)
|
||||
+ [canvas] Add support for vrt.be/vrtnu (#11873)
|
||||
* [twitch:clips] Fix title extraction (#14566)
|
||||
+ [ndtv] Add support for sub-sites (#14534)
|
||||
* [dramafever] Fix login error message extraction
|
||||
+ [nick] Add support for more nickelodeon sites (no, dk, se, ch, fr, es, pt,
|
||||
ro, hu) (#14553)
|
||||
|
||||
|
||||
version 2017.10.20
|
||||
|
||||
Core
|
||||
|
@@ -1,3 +1,5 @@
|
||||
[](https://travis-ci.org/rg3/youtube-dl)
|
||||
|
||||
youtube-dl - download videos from youtube.com or other video platforms
|
||||
|
||||
- [INSTALLATION](#installation)
|
||||
|
@@ -498,7 +498,6 @@
|
||||
- **MySpace:album**
|
||||
- **MySpass**
|
||||
- **Myvi**
|
||||
- **myvideo** (Currently broken)
|
||||
- **MyVidster**
|
||||
- **n-tv.de**
|
||||
- **natgeo**
|
||||
@@ -977,6 +976,7 @@
|
||||
- **vpro**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
||||
- **Vrak**
|
||||
- **VRT**: deredactie.be, sporza.be, cobra.be and cobra.canvas.be
|
||||
- **VrtNU**: VrtNU.be
|
||||
- **vrv**
|
||||
- **vrv:series**
|
||||
- **VShare**
|
||||
@@ -1035,6 +1035,9 @@
|
||||
- **YouJizz**
|
||||
- **youku**: 优酷
|
||||
- **youku:show**
|
||||
- **YouNowChannel**
|
||||
- **YouNowLive**
|
||||
- **YouNowMoment**
|
||||
- **YouPorn**
|
||||
- **YourUpload**
|
||||
- **youtube**: YouTube.com
|
||||
|
@@ -540,6 +540,7 @@ class TestUtil(unittest.TestCase):
|
||||
self.assertEqual(parse_duration('87 Min.'), 5220)
|
||||
self.assertEqual(parse_duration('PT1H0.040S'), 3600.04)
|
||||
self.assertEqual(parse_duration('PT00H03M30SZ'), 210)
|
||||
self.assertEqual(parse_duration('P0Y0M0DT0H4M20.880S'), 260.88)
|
||||
|
||||
def test_fix_xml_ampersands(self):
|
||||
self.assertEqual(
|
||||
|
@@ -47,7 +47,7 @@ class AZMedienIE(AZMedienBaseIE):
|
||||
'url': 'http://www.telezueri.ch/62-show-zuerinews/13772-episode-sonntag-18-dezember-2016/32419-segment-massenabweisungen-beim-hiltl-club-wegen-pelzboom',
|
||||
'info_dict': {
|
||||
'id': '1_2444peh4',
|
||||
'ext': 'mov',
|
||||
'ext': 'mp4',
|
||||
'title': 'Massenabweisungen beim Hiltl Club wegen Pelzboom',
|
||||
'description': 'md5:9ea9dd1b159ad65b36ddcf7f0d7c76a8',
|
||||
'uploader_id': 'TeleZ?ri',
|
||||
|
@@ -1,16 +1,22 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import json
|
||||
|
||||
from .common import InfoExtractor
|
||||
from .gigya import GigyaBaseIE
|
||||
from ..compat import compat_HTTPError
|
||||
from ..utils import (
|
||||
float_or_none,
|
||||
ExtractorError,
|
||||
strip_or_none,
|
||||
float_or_none,
|
||||
int_or_none,
|
||||
parse_iso8601,
|
||||
)
|
||||
|
||||
|
||||
class CanvasIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://mediazone\.vrt\.be/api/v1/(?P<site_id>canvas|een|ketnet)/assets/(?P<id>m[dz]-ast-[^/?#&]+)'
|
||||
_VALID_URL = r'https?://mediazone\.vrt\.be/api/v1/(?P<site_id>canvas|een|ketnet|vrtvideo)/assets/(?P<id>[^/?#&]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://mediazone.vrt.be/api/v1/ketnet/assets/md-ast-4ac54990-ce66-4d00-a8ca-9eac86f4c475',
|
||||
'md5': '90139b746a0a9bd7bb631283f6e2a64e',
|
||||
@@ -166,3 +172,139 @@ class CanvasEenIE(InfoExtractor):
|
||||
'title': title,
|
||||
'description': self._og_search_description(webpage),
|
||||
}
|
||||
|
||||
|
||||
class VrtNUIE(GigyaBaseIE):
|
||||
IE_DESC = 'VrtNU.be'
|
||||
_VALID_URL = r'https?://(?:www\.)?vrt\.be/(?P<site_id>vrtnu)/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.vrt.be/vrtnu/a-z/postbus-x/1/postbus-x-s1a1/',
|
||||
'info_dict': {
|
||||
'id': 'pbs-pub-2e2d8c27-df26-45c9-9dc6-90c78153044d$vid-90c932b1-e21d-4fb8-99b1-db7b49cf74de',
|
||||
'ext': 'flv',
|
||||
'title': 'De zwarte weduwe',
|
||||
'description': 'md5:d90c21dced7db869a85db89a623998d4',
|
||||
'duration': 1457.04,
|
||||
'thumbnail': r're:^https?://.*\.jpg$',
|
||||
'season': '1',
|
||||
'season_number': 1,
|
||||
'episode_number': 1,
|
||||
},
|
||||
'skip': 'This video is only available for registered users'
|
||||
}]
|
||||
_NETRC_MACHINE = 'vrtnu'
|
||||
_APIKEY = '3_0Z2HujMtiWq_pkAjgnS2Md2E11a1AwZjYiBETtwNE-EoEHDINgtnvcAOpNgmrVGy'
|
||||
_CONTEXT_ID = 'R3595707040'
|
||||
|
||||
def _real_initialize(self):
|
||||
self._login()
|
||||
|
||||
def _login(self):
|
||||
username, password = self._get_login_info()
|
||||
if username is None:
|
||||
return
|
||||
|
||||
auth_data = {
|
||||
'APIKey': self._APIKEY,
|
||||
'targetEnv': 'jssdk',
|
||||
'loginID': username,
|
||||
'password': password,
|
||||
'authMode': 'cookie',
|
||||
}
|
||||
|
||||
auth_info = self._gigya_login(auth_data)
|
||||
|
||||
# Sometimes authentication fails for no good reason, retry
|
||||
login_attempt = 1
|
||||
while login_attempt <= 3:
|
||||
try:
|
||||
# When requesting a token, no actual token is returned, but the
|
||||
# necessary cookies are set.
|
||||
self._request_webpage(
|
||||
'https://token.vrt.be',
|
||||
None, note='Requesting a token', errnote='Could not get a token',
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
'Referer': 'https://www.vrt.be/vrtnu/',
|
||||
},
|
||||
data=json.dumps({
|
||||
'uid': auth_info['UID'],
|
||||
'uidsig': auth_info['UIDSignature'],
|
||||
'ts': auth_info['signatureTimestamp'],
|
||||
'email': auth_info['profile']['email'],
|
||||
}).encode('utf-8'))
|
||||
except ExtractorError as e:
|
||||
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
|
||||
login_attempt += 1
|
||||
self.report_warning('Authentication failed')
|
||||
self._sleep(1, None, msg_template='Waiting for %(timeout)s seconds before trying again')
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
break
|
||||
|
||||
def _real_extract(self, url):
|
||||
display_id = self._match_id(url)
|
||||
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
|
||||
title = self._html_search_regex(
|
||||
r'(?ms)<h1 class="content__heading">(.+?)</h1>',
|
||||
webpage, 'title').strip()
|
||||
|
||||
description = self._html_search_regex(
|
||||
r'(?ms)<div class="content__description">(.+?)</div>',
|
||||
webpage, 'description', default=None)
|
||||
|
||||
season = self._html_search_regex(
|
||||
[r'''(?xms)<div\ class="tabs__tab\ tabs__tab--active">\s*
|
||||
<span>seizoen\ (.+?)</span>\s*
|
||||
</div>''',
|
||||
r'<option value="seizoen (\d{1,3})" data-href="[^"]+?" selected>'],
|
||||
webpage, 'season', default=None)
|
||||
|
||||
season_number = int_or_none(season)
|
||||
|
||||
episode_number = int_or_none(self._html_search_regex(
|
||||
r'''(?xms)<div\ class="content__episode">\s*
|
||||
<abbr\ title="aflevering">afl</abbr>\s*<span>(\d+)</span>
|
||||
</div>''',
|
||||
webpage, 'episode_number', default=None))
|
||||
|
||||
release_date = parse_iso8601(self._html_search_regex(
|
||||
r'(?ms)<div class="content__broadcastdate">\s*<time\ datetime="(.+?)"',
|
||||
webpage, 'release_date', default=None))
|
||||
|
||||
# If there's a ? or a # in the URL, remove them and everything after
|
||||
clean_url = url.split('?')[0].split('#')[0].strip('/')
|
||||
securevideo_url = clean_url + '.mssecurevideo.json'
|
||||
|
||||
try:
|
||||
video = self._download_json(securevideo_url, display_id)
|
||||
except ExtractorError as e:
|
||||
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
|
||||
self.raise_login_required()
|
||||
raise
|
||||
|
||||
# We are dealing with a '../<show>.relevant' URL
|
||||
redirect_url = video.get('url')
|
||||
if redirect_url:
|
||||
return self.url_result(self._proto_relative_url(redirect_url, 'https:'))
|
||||
|
||||
# There is only one entry, but with an unknown key, so just get
|
||||
# the first one
|
||||
video_id = list(video.values())[0].get('videoid')
|
||||
|
||||
return {
|
||||
'_type': 'url_transparent',
|
||||
'url': 'https://mediazone.vrt.be/api/v1/vrtvideo/assets/%s' % video_id,
|
||||
'ie_key': CanvasIE.ie_key(),
|
||||
'id': video_id,
|
||||
'display_id': display_id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'season': season,
|
||||
'season_number': season_number,
|
||||
'episode_number': episode_number,
|
||||
'release_date': release_date,
|
||||
}
|
||||
|
@@ -1401,7 +1401,7 @@ class InfoExtractor(object):
|
||||
media_url = media.get('URI')
|
||||
if media_url:
|
||||
format_id = []
|
||||
for v in (group_id, name):
|
||||
for v in (m3u8_id, group_id, name):
|
||||
if v:
|
||||
format_id.append(v)
|
||||
f = {
|
||||
|
@@ -2,53 +2,85 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import unified_strdate
|
||||
from ..compat import compat_str
|
||||
from ..utils import (
|
||||
float_or_none,
|
||||
unified_strdate,
|
||||
)
|
||||
|
||||
|
||||
class DctpTvIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?dctp\.tv/(#/)?filme/(?P<id>.+?)/$'
|
||||
_VALID_URL = r'https?://(?:www\.)?dctp\.tv/(?:#/)?filme/(?P<id>[^/?#&]+)'
|
||||
_TEST = {
|
||||
'url': 'http://www.dctp.tv/filme/videoinstallation-fuer-eine-kaufhausfassade/',
|
||||
'md5': '174dd4a8a6225cf5655952f969cfbe24',
|
||||
'info_dict': {
|
||||
'id': '95eaa4f33dad413aa17b4ee613cccc6c',
|
||||
'display_id': 'videoinstallation-fuer-eine-kaufhausfassade',
|
||||
'ext': 'mp4',
|
||||
'ext': 'flv',
|
||||
'title': 'Videoinstallation für eine Kaufhausfassade',
|
||||
'description': 'Kurzfilm',
|
||||
'upload_date': '20110407',
|
||||
'thumbnail': r're:^https?://.*\.jpg$',
|
||||
'duration': 71.24,
|
||||
},
|
||||
'params': {
|
||||
# rtmp download
|
||||
'skip_download': True,
|
||||
},
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
display_id = self._match_id(url)
|
||||
|
||||
object_id = self._html_search_meta('DC.identifier', webpage)
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
|
||||
servers_json = self._download_json(
|
||||
'http://www.dctp.tv/elastic_streaming_client/get_streaming_server/',
|
||||
video_id, note='Downloading server list')
|
||||
server = servers_json[0]['server']
|
||||
m3u8_path = self._search_regex(
|
||||
r'\'([^\'"]+/playlist\.m3u8)"', webpage, 'm3u8 path')
|
||||
formats = self._extract_m3u8_formats(
|
||||
'http://%s%s' % (server, m3u8_path), video_id, ext='mp4',
|
||||
entry_protocol='m3u8_native')
|
||||
video_id = self._html_search_meta(
|
||||
'DC.identifier', webpage, 'video id',
|
||||
default=None) or self._search_regex(
|
||||
r'id=["\']uuid[^>]+>([^<]+)<', webpage, 'video id')
|
||||
|
||||
title = self._og_search_title(webpage)
|
||||
|
||||
servers = self._download_json(
|
||||
'http://www.dctp.tv/streaming_servers/', display_id,
|
||||
note='Downloading server list', fatal=False)
|
||||
|
||||
if servers:
|
||||
endpoint = next(
|
||||
server['endpoint']
|
||||
for server in servers
|
||||
if isinstance(server.get('endpoint'), compat_str) and
|
||||
'cloudfront' in server['endpoint'])
|
||||
else:
|
||||
endpoint = 'rtmpe://s2pqqn4u96e4j8.cloudfront.net/cfx/st/'
|
||||
|
||||
app = self._search_regex(
|
||||
r'^rtmpe?://[^/]+/(?P<app>.*)$', endpoint, 'app')
|
||||
|
||||
formats = [{
|
||||
'url': endpoint,
|
||||
'app': app,
|
||||
'play_path': 'mp4:%s_dctp_0500_4x3.m4v' % video_id,
|
||||
'page_url': url,
|
||||
'player_url': 'http://svm-prod-dctptv-static.s3.amazonaws.com/dctptv-relaunch2012-109.swf',
|
||||
'ext': 'flv',
|
||||
}]
|
||||
|
||||
description = self._html_search_meta('DC.description', webpage)
|
||||
upload_date = unified_strdate(
|
||||
self._html_search_meta('DC.date.created', webpage))
|
||||
thumbnail = self._og_search_thumbnail(webpage)
|
||||
duration = float_or_none(self._search_regex(
|
||||
r'id=["\']duration_in_ms[^+]>(\d+)', webpage, 'duration',
|
||||
default=None), scale=1000)
|
||||
|
||||
return {
|
||||
'id': object_id,
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'formats': formats,
|
||||
'display_id': video_id,
|
||||
'display_id': display_id,
|
||||
'description': description,
|
||||
'upload_date': upload_date,
|
||||
'thumbnail': thumbnail,
|
||||
'duration': duration,
|
||||
}
|
||||
|
@@ -59,7 +59,7 @@ class DramaFeverBaseIE(AMPIE):
|
||||
if all(logout_pattern not in response
|
||||
for logout_pattern in ['href="/accounts/logout/"', '>Log out<']):
|
||||
error = self._html_search_regex(
|
||||
r'(?s)class="hidden-xs prompt"[^>]*>(.+?)<',
|
||||
r'(?s)<h\d[^>]+\bclass="hidden-xs prompt"[^>]*>(.+?)</h\d',
|
||||
response, 'error message', default=None)
|
||||
if error:
|
||||
raise ExtractorError('Unable to login: %s' % error, expected=True)
|
||||
|
@@ -2,7 +2,9 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..compat import compat_str
|
||||
from ..utils import (
|
||||
determine_ext,
|
||||
int_or_none,
|
||||
try_get,
|
||||
unified_timestamp,
|
||||
@@ -17,7 +19,7 @@ class EggheadCourseIE(InfoExtractor):
|
||||
'url': 'https://egghead.io/courses/professor-frisby-introduces-composable-functional-javascript',
|
||||
'playlist_count': 29,
|
||||
'info_dict': {
|
||||
'id': 'professor-frisby-introduces-composable-functional-javascript',
|
||||
'id': '72',
|
||||
'title': 'Professor Frisby Introduces Composable Functional JavaScript',
|
||||
'description': 're:(?s)^This course teaches the ubiquitous.*You\'ll start composing functionality before you know it.$',
|
||||
},
|
||||
@@ -26,14 +28,28 @@ class EggheadCourseIE(InfoExtractor):
|
||||
def _real_extract(self, url):
|
||||
playlist_id = self._match_id(url)
|
||||
|
||||
course = self._download_json(
|
||||
'https://egghead.io/api/v1/series/%s' % playlist_id, playlist_id)
|
||||
lessons = self._download_json(
|
||||
'https://egghead.io/api/v1/series/%s/lessons' % playlist_id,
|
||||
playlist_id, 'Downloading course lessons JSON')
|
||||
|
||||
entries = [
|
||||
self.url_result(
|
||||
'wistia:%s' % lesson['wistia_id'], ie='Wistia',
|
||||
video_id=lesson['wistia_id'], video_title=lesson.get('title'))
|
||||
for lesson in course['lessons'] if lesson.get('wistia_id')]
|
||||
entries = []
|
||||
for lesson in lessons:
|
||||
lesson_url = lesson.get('http_url')
|
||||
if not lesson_url or not isinstance(lesson_url, compat_str):
|
||||
continue
|
||||
lesson_id = lesson.get('id')
|
||||
if lesson_id:
|
||||
lesson_id = compat_str(lesson_id)
|
||||
entries.append(self.url_result(
|
||||
lesson_url, ie=EggheadLessonIE.ie_key(), video_id=lesson_id))
|
||||
|
||||
course = self._download_json(
|
||||
'https://egghead.io/api/v1/series/%s' % playlist_id,
|
||||
playlist_id, 'Downloading course JSON', fatal=False) or {}
|
||||
|
||||
playlist_id = course.get('id')
|
||||
if playlist_id:
|
||||
playlist_id = compat_str(playlist_id)
|
||||
|
||||
return self.playlist_result(
|
||||
entries, playlist_id, course.get('title'),
|
||||
@@ -43,11 +59,12 @@ class EggheadCourseIE(InfoExtractor):
|
||||
class EggheadLessonIE(InfoExtractor):
|
||||
IE_DESC = 'egghead.io lesson'
|
||||
IE_NAME = 'egghead:lesson'
|
||||
_VALID_URL = r'https://egghead\.io/lessons/(?P<id>[^/?#&]+)'
|
||||
_TEST = {
|
||||
_VALID_URL = r'https://egghead\.io/(?:api/v1/)?lessons/(?P<id>[^/?#&]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://egghead.io/lessons/javascript-linear-data-flow-with-container-style-types-box',
|
||||
'info_dict': {
|
||||
'id': 'fv5yotjxcg',
|
||||
'id': '1196',
|
||||
'display_id': 'javascript-linear-data-flow-with-container-style-types-box',
|
||||
'ext': 'mp4',
|
||||
'title': 'Create linear data flow with container style types (Box)',
|
||||
'description': 'md5:9aa2cdb6f9878ed4c39ec09e85a8150e',
|
||||
@@ -60,25 +77,51 @@ class EggheadLessonIE(InfoExtractor):
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
'format': 'bestvideo',
|
||||
},
|
||||
}
|
||||
}, {
|
||||
'url': 'https://egghead.io/api/v1/lessons/react-add-redux-to-a-react-application',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
lesson_id = self._match_id(url)
|
||||
display_id = self._match_id(url)
|
||||
|
||||
lesson = self._download_json(
|
||||
'https://egghead.io/api/v1/lessons/%s' % lesson_id, lesson_id)
|
||||
'https://egghead.io/api/v1/lessons/%s' % display_id, display_id)
|
||||
|
||||
lesson_id = compat_str(lesson['id'])
|
||||
title = lesson['title']
|
||||
|
||||
formats = []
|
||||
for _, format_url in lesson['media_urls'].items():
|
||||
if not format_url or not isinstance(format_url, compat_str):
|
||||
continue
|
||||
ext = determine_ext(format_url)
|
||||
if ext == 'm3u8':
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
format_url, lesson_id, 'mp4', entry_protocol='m3u8',
|
||||
m3u8_id='hls', fatal=False))
|
||||
elif ext == 'mpd':
|
||||
formats.extend(self._extract_mpd_formats(
|
||||
format_url, lesson_id, mpd_id='dash', fatal=False))
|
||||
else:
|
||||
formats.append({
|
||||
'url': format_url,
|
||||
})
|
||||
self._sort_formats(formats)
|
||||
|
||||
return {
|
||||
'_type': 'url_transparent',
|
||||
'ie_key': 'Wistia',
|
||||
'url': 'wistia:%s' % lesson['wistia_id'],
|
||||
'id': lesson['wistia_id'],
|
||||
'title': lesson.get('title'),
|
||||
'id': lesson_id,
|
||||
'display_id': display_id,
|
||||
'title': title,
|
||||
'description': lesson.get('summary'),
|
||||
'thumbnail': lesson.get('thumb_nail'),
|
||||
'timestamp': unified_timestamp(lesson.get('published_at')),
|
||||
'duration': int_or_none(lesson.get('duration')),
|
||||
'view_count': int_or_none(lesson.get('plays_count')),
|
||||
'tags': try_get(lesson, lambda x: x['tag_list'], list),
|
||||
'series': try_get(
|
||||
lesson, lambda x: x['series']['title'], compat_str),
|
||||
'formats': formats,
|
||||
}
|
||||
|
@@ -150,6 +150,7 @@ from .canalc2 import Canalc2IE
|
||||
from .canvas import (
|
||||
CanvasIE,
|
||||
CanvasEenIE,
|
||||
VrtNUIE,
|
||||
)
|
||||
from .carambatv import (
|
||||
CarambaTVIE,
|
||||
@@ -623,7 +624,6 @@ from .mwave import MwaveIE, MwaveMeetGreetIE
|
||||
from .myspace import MySpaceIE, MySpaceAlbumIE
|
||||
from .myspass import MySpassIE
|
||||
from .myvi import MyviIE
|
||||
from .myvideo import MyVideoIE
|
||||
from .myvidster import MyVidsterIE
|
||||
from .nationalgeographic import (
|
||||
NationalGeographicVideoIE,
|
||||
@@ -1335,6 +1335,11 @@ from .youku import (
|
||||
YoukuIE,
|
||||
YoukuShowIE,
|
||||
)
|
||||
from .younow import (
|
||||
YouNowLiveIE,
|
||||
YouNowChannelIE,
|
||||
YouNowMomentIE,
|
||||
)
|
||||
from .youporn import YouPornIE
|
||||
from .yourupload import YourUploadIE
|
||||
from .youtube import (
|
||||
|
@@ -3,27 +3,31 @@ from __future__ import unicode_literals
|
||||
|
||||
from .adobepass import AdobePassIE
|
||||
from ..utils import (
|
||||
update_url_query,
|
||||
extract_attributes,
|
||||
int_or_none,
|
||||
parse_age_limit,
|
||||
smuggle_url,
|
||||
update_url_query,
|
||||
)
|
||||
|
||||
|
||||
class FXNetworksIE(AdobePassIE):
|
||||
_VALID_URL = r'https?://(?:www\.)?(?:fxnetworks|simpsonsworld)\.com/video/(?P<id>\d+)'
|
||||
_TESTS = [{
|
||||
'url': 'http://www.fxnetworks.com/video/719841347694',
|
||||
'md5': '1447d4722e42ebca19e5232ab93abb22',
|
||||
'url': 'http://www.fxnetworks.com/video/1032565827847',
|
||||
'md5': '8d99b97b4aa7a202f55b6ed47ea7e703',
|
||||
'info_dict': {
|
||||
'id': '719841347694',
|
||||
'id': 'dRzwHC_MMqIv',
|
||||
'ext': 'mp4',
|
||||
'title': 'Vanpage',
|
||||
'description': 'F*ck settling down. You\'re the Worst returns for an all new season August 31st on FXX.',
|
||||
'title': 'First Look: Better Things - Season 2',
|
||||
'description': 'Because real life is like a fart. Watch this FIRST LOOK to see what inspired the new season of Better Things.',
|
||||
'age_limit': 14,
|
||||
'uploader': 'NEWA-FNG-FX',
|
||||
'upload_date': '20160706',
|
||||
'timestamp': 1467844741,
|
||||
'upload_date': '20170825',
|
||||
'timestamp': 1503686274,
|
||||
'episode_number': 0,
|
||||
'season_number': 2,
|
||||
'series': 'Better Things',
|
||||
},
|
||||
'add_ie': ['ThePlatform'],
|
||||
}, {
|
||||
@@ -64,6 +68,9 @@ class FXNetworksIE(AdobePassIE):
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'url': smuggle_url(update_url_query(release_url, query), {'force_smil_url': True}),
|
||||
'series': video_data.get('data-show-title'),
|
||||
'episode_number': int_or_none(video_data.get('data-episode')),
|
||||
'season_number': int_or_none(video_data.get('data-season')),
|
||||
'thumbnail': video_data.get('data-large-thumb'),
|
||||
'age_limit': parse_age_limit(rating),
|
||||
'ie_key': 'ThePlatform',
|
||||
|
22
youtube_dl/extractor/gigya.py
Normal file
22
youtube_dl/extractor/gigya.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .common import InfoExtractor
|
||||
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
urlencode_postdata,
|
||||
)
|
||||
|
||||
|
||||
class GigyaBaseIE(InfoExtractor):
|
||||
def _gigya_login(self, auth_data):
|
||||
auth_info = self._download_json(
|
||||
'https://accounts.eu1.gigya.com/accounts.login', None,
|
||||
note='Logging in', errnote='Unable to log in',
|
||||
data=urlencode_postdata(auth_data))
|
||||
|
||||
error_message = auth_info.get('errorDetails') or auth_info.get('errorMessage')
|
||||
if error_message:
|
||||
raise ExtractorError(
|
||||
'Unable to login: %s' % error_message, expected=True)
|
||||
return auth_info
|
@@ -2,19 +2,18 @@ from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from .gigya import GigyaBaseIE
|
||||
|
||||
from ..compat import compat_str
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
int_or_none,
|
||||
parse_duration,
|
||||
try_get,
|
||||
unified_timestamp,
|
||||
urlencode_postdata,
|
||||
)
|
||||
|
||||
|
||||
class MedialaanIE(InfoExtractor):
|
||||
class MedialaanIE(GigyaBaseIE):
|
||||
_VALID_URL = r'''(?x)
|
||||
https?://
|
||||
(?:www\.|nieuws\.)?
|
||||
@@ -119,15 +118,7 @@ class MedialaanIE(InfoExtractor):
|
||||
'password': password,
|
||||
}
|
||||
|
||||
auth_info = self._download_json(
|
||||
'https://accounts.eu1.gigya.com/accounts.login', None,
|
||||
note='Logging in', errnote='Unable to log in',
|
||||
data=urlencode_postdata(auth_data))
|
||||
|
||||
error_message = auth_info.get('errorDetails') or auth_info.get('errorMessage')
|
||||
if error_message:
|
||||
raise ExtractorError(
|
||||
'Unable to login: %s' % error_message, expected=True)
|
||||
auth_info = self._gigya_login(auth_data)
|
||||
|
||||
self._uid = auth_info['UID']
|
||||
self._uid_signature = auth_info['UIDSignature']
|
||||
|
@@ -1,177 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import binascii
|
||||
import base64
|
||||
import hashlib
|
||||
import re
|
||||
import json
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..compat import (
|
||||
compat_ord,
|
||||
compat_urllib_parse_unquote,
|
||||
compat_urllib_parse_urlencode,
|
||||
)
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
sanitized_Request,
|
||||
)
|
||||
|
||||
|
||||
class MyVideoIE(InfoExtractor):
|
||||
_WORKING = False
|
||||
_VALID_URL = r'https?://(?:www\.)?myvideo\.de/(?:[^/]+/)?watch/(?P<id>[0-9]+)/[^?/]+.*'
|
||||
IE_NAME = 'myvideo'
|
||||
_TEST = {
|
||||
'url': 'http://www.myvideo.de/watch/8229274/bowling_fail_or_win',
|
||||
'md5': '2d2753e8130479ba2cb7e0a37002053e',
|
||||
'info_dict': {
|
||||
'id': '8229274',
|
||||
'ext': 'flv',
|
||||
'title': 'bowling-fail-or-win',
|
||||
}
|
||||
}
|
||||
|
||||
# Original Code from: https://github.com/dersphere/plugin.video.myvideo_de.git
|
||||
# Released into the Public Domain by Tristan Fischer on 2013-05-19
|
||||
# https://github.com/rg3/youtube-dl/pull/842
|
||||
def __rc4crypt(self, data, key):
|
||||
x = 0
|
||||
box = list(range(256))
|
||||
for i in list(range(256)):
|
||||
x = (x + box[i] + compat_ord(key[i % len(key)])) % 256
|
||||
box[i], box[x] = box[x], box[i]
|
||||
x = 0
|
||||
y = 0
|
||||
out = ''
|
||||
for char in data:
|
||||
x = (x + 1) % 256
|
||||
y = (y + box[x]) % 256
|
||||
box[x], box[y] = box[y], box[x]
|
||||
out += chr(compat_ord(char) ^ box[(box[x] + box[y]) % 256])
|
||||
return out
|
||||
|
||||
def __md5(self, s):
|
||||
return hashlib.md5(s).hexdigest().encode()
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
|
||||
GK = (
|
||||
b'WXpnME1EZGhNRGhpTTJNM01XVmhOREU0WldNNVpHTTJOakpt'
|
||||
b'TW1FMU5tVTBNR05pWkRaa05XRXhNVFJoWVRVd1ptSXhaVEV3'
|
||||
b'TnpsbA0KTVRkbU1tSTRNdz09'
|
||||
)
|
||||
|
||||
# Get video webpage
|
||||
webpage_url = 'http://www.myvideo.de/watch/%s' % video_id
|
||||
webpage = self._download_webpage(webpage_url, video_id)
|
||||
|
||||
mobj = re.search('source src=\'(.+?)[.]([^.]+)\'', webpage)
|
||||
if mobj is not None:
|
||||
self.report_extraction(video_id)
|
||||
video_url = mobj.group(1) + '.flv'
|
||||
|
||||
video_title = self._html_search_regex('<title>([^<]+)</title>',
|
||||
webpage, 'title')
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'url': video_url,
|
||||
'title': video_title,
|
||||
}
|
||||
|
||||
mobj = re.search(r'data-video-service="/service/data/video/%s/config' % video_id, webpage)
|
||||
if mobj is not None:
|
||||
request = sanitized_Request('http://www.myvideo.de/service/data/video/%s/config' % video_id, '')
|
||||
response = self._download_webpage(request, video_id,
|
||||
'Downloading video info')
|
||||
info = json.loads(base64.b64decode(response).decode('utf-8'))
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': info['title'],
|
||||
'url': info['streaming_url'].replace('rtmpe', 'rtmpt'),
|
||||
'play_path': info['filename'],
|
||||
'ext': 'flv',
|
||||
'thumbnail': info['thumbnail'][0]['url'],
|
||||
}
|
||||
|
||||
# try encxml
|
||||
mobj = re.search('var flashvars={(.+?)}', webpage)
|
||||
if mobj is None:
|
||||
raise ExtractorError('Unable to extract video')
|
||||
|
||||
params = {}
|
||||
encxml = ''
|
||||
sec = mobj.group(1)
|
||||
for (a, b) in re.findall('(.+?):\'(.+?)\',?', sec):
|
||||
if not a == '_encxml':
|
||||
params[a] = b
|
||||
else:
|
||||
encxml = compat_urllib_parse_unquote(b)
|
||||
if not params.get('domain'):
|
||||
params['domain'] = 'www.myvideo.de'
|
||||
xmldata_url = '%s?%s' % (encxml, compat_urllib_parse_urlencode(params))
|
||||
if 'flash_playertype=MTV' in xmldata_url:
|
||||
self._downloader.report_warning('avoiding MTV player')
|
||||
xmldata_url = (
|
||||
'http://www.myvideo.de/dynamic/get_player_video_xml.php'
|
||||
'?flash_playertype=D&ID=%s&_countlimit=4&autorun=yes'
|
||||
) % video_id
|
||||
|
||||
# get enc data
|
||||
enc_data = self._download_webpage(xmldata_url, video_id).split('=')[1]
|
||||
enc_data_b = binascii.unhexlify(enc_data)
|
||||
sk = self.__md5(
|
||||
base64.b64decode(base64.b64decode(GK)) +
|
||||
self.__md5(
|
||||
str(video_id).encode('utf-8')
|
||||
)
|
||||
)
|
||||
dec_data = self.__rc4crypt(enc_data_b, sk)
|
||||
|
||||
# extracting infos
|
||||
self.report_extraction(video_id)
|
||||
|
||||
video_url = None
|
||||
mobj = re.search('connectionurl=\'(.*?)\'', dec_data)
|
||||
if mobj:
|
||||
video_url = compat_urllib_parse_unquote(mobj.group(1))
|
||||
if 'myvideo2flash' in video_url:
|
||||
self.report_warning(
|
||||
'Rewriting URL to use unencrypted rtmp:// ...',
|
||||
video_id)
|
||||
video_url = video_url.replace('rtmpe://', 'rtmp://')
|
||||
|
||||
if not video_url:
|
||||
# extract non rtmp videos
|
||||
mobj = re.search('path=\'(http.*?)\' source=\'(.*?)\'', dec_data)
|
||||
if mobj is None:
|
||||
raise ExtractorError('unable to extract url')
|
||||
video_url = compat_urllib_parse_unquote(mobj.group(1)) + compat_urllib_parse_unquote(mobj.group(2))
|
||||
|
||||
video_file = self._search_regex('source=\'(.*?)\'', dec_data, 'video file')
|
||||
video_file = compat_urllib_parse_unquote(video_file)
|
||||
|
||||
if not video_file.endswith('f4m'):
|
||||
ppath, prefix = video_file.split('.')
|
||||
video_playpath = '%s:%s' % (prefix, ppath)
|
||||
else:
|
||||
video_playpath = ''
|
||||
|
||||
video_swfobj = self._search_regex(r'swfobject\.embedSWF\(\'(.+?)\'', webpage, 'swfobj')
|
||||
video_swfobj = compat_urllib_parse_unquote(video_swfobj)
|
||||
|
||||
video_title = self._html_search_regex("<h1(?: class='globalHd')?>(.*?)</h1>",
|
||||
webpage, 'title')
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'url': video_url,
|
||||
'tc_url': video_url,
|
||||
'title': video_title,
|
||||
'ext': 'flv',
|
||||
'play_path': video_playpath,
|
||||
'player_url': video_swfobj,
|
||||
}
|
@@ -15,7 +15,7 @@ from ..utils import (
|
||||
|
||||
|
||||
class NBCIE(AdobePassIE):
|
||||
_VALID_URL = r'https?(?P<permalink>://(?:www\.)?nbc\.com/[^/]+/video/[^/]+/(?P<id>n?\d+))'
|
||||
_VALID_URL = r'https?(?P<permalink>://(?:www\.)?nbc\.com/(?:classic-tv/)?[^/]+/video/[^/]+/(?P<id>n?\d+))'
|
||||
|
||||
_TESTS = [
|
||||
{
|
||||
@@ -67,7 +67,11 @@ class NBCIE(AdobePassIE):
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'Only works from US',
|
||||
}
|
||||
},
|
||||
{
|
||||
'url': 'https://www.nbc.com/classic-tv/charles-in-charge/video/charles-in-charge-pilot/n3310',
|
||||
'only_matching': True,
|
||||
},
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
@@ -1,45 +1,106 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..compat import (
|
||||
compat_urllib_parse_unquote_plus
|
||||
)
|
||||
from ..utils import (
|
||||
int_or_none,
|
||||
parse_duration,
|
||||
remove_end,
|
||||
unified_strdate,
|
||||
urljoin
|
||||
)
|
||||
|
||||
|
||||
class NDTVIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?ndtv\.com/video/(?:[^/]+/)+[^/?^&]+-(?P<id>\d+)'
|
||||
_VALID_URL = r'https?://(?:[^/]+\.)?ndtv\.com/(?:[^/]+/)*videos?/?(?:[^/]+/)*[^/?^&]+-(?P<id>\d+)'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://www.ndtv.com/video/news/news/ndtv-exclusive-don-t-need-character-certificate-from-rahul-gandhi-says-arvind-kejriwal-300710',
|
||||
'md5': '39f992dbe5fb531c395d8bbedb1e5e88',
|
||||
'info_dict': {
|
||||
'id': '300710',
|
||||
'ext': 'mp4',
|
||||
'title': "NDTV exclusive: Don't need character certificate from Rahul Gandhi, says Arvind Kejriwal",
|
||||
'description': 'md5:ab2d4b4a6056c5cb4caa6d729deabf02',
|
||||
'upload_date': '20131208',
|
||||
'duration': 1327,
|
||||
'thumbnail': r're:https?://.*\.jpg',
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'https://khabar.ndtv.com/video/show/prime-time/prime-time-ill-system-and-poor-education-468818',
|
||||
'md5': '78efcf3880ef3fd9b83d405ca94a38eb',
|
||||
'info_dict': {
|
||||
'id': '468818',
|
||||
'ext': 'mp4',
|
||||
'title': "प्राइम टाइम: सिस्टम बीमार, स्कूल बदहाल",
|
||||
'description': 'md5:f410512f1b49672e5695dea16ef2731d',
|
||||
'upload_date': '20170928',
|
||||
'duration': 2218,
|
||||
'thumbnail': r're:https?://.*\.jpg',
|
||||
}
|
||||
},
|
||||
}
|
||||
{
|
||||
# __filename is url
|
||||
'url': 'http://movies.ndtv.com/videos/cracker-free-diwali-wishes-from-karan-johar-kriti-sanon-other-stars-470304',
|
||||
'md5': 'f1d709352305b44443515ac56b45aa46',
|
||||
'info_dict': {
|
||||
'id': '470304',
|
||||
'ext': 'mp4',
|
||||
'title': "Cracker-Free Diwali Wishes From Karan Johar, Kriti Sanon & Other Stars",
|
||||
'description': 'md5:f115bba1adf2f6433fa7c1ade5feb465',
|
||||
'upload_date': '20171019',
|
||||
'duration': 137,
|
||||
'thumbnail': r're:https?://.*\.jpg',
|
||||
}
|
||||
},
|
||||
{
|
||||
'url': 'https://www.ndtv.com/video/news/news/delhi-s-air-quality-status-report-after-diwali-is-very-poor-470372',
|
||||
'only_matching': True
|
||||
},
|
||||
{
|
||||
'url': 'https://auto.ndtv.com/videos/the-cnb-daily-october-13-2017-469935',
|
||||
'only_matching': True
|
||||
},
|
||||
{
|
||||
'url': 'https://sports.ndtv.com/cricket/videos/2nd-t20i-rock-thrown-at-australia-cricket-team-bus-after-win-over-india-469764',
|
||||
'only_matching': True
|
||||
},
|
||||
{
|
||||
'url': 'http://gadgets.ndtv.com/videos/uncharted-the-lost-legacy-review-465568',
|
||||
'only_matching': True
|
||||
},
|
||||
{
|
||||
'url': 'http://profit.ndtv.com/videos/news/video-indian-economy-on-very-solid-track-international-monetary-fund-chief-470040',
|
||||
'only_matching': True
|
||||
},
|
||||
{
|
||||
'url': 'http://food.ndtv.com/video-basil-seeds-coconut-porridge-419083',
|
||||
'only_matching': True
|
||||
},
|
||||
{
|
||||
'url': 'https://doctor.ndtv.com/videos/top-health-stories-of-the-week-467396',
|
||||
'only_matching': True
|
||||
},
|
||||
{
|
||||
'url': 'https://swirlster.ndtv.com/video/how-to-make-friends-at-work-469324',
|
||||
'only_matching': True
|
||||
}
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
title = remove_end(self._og_search_title(webpage), ' - NDTV')
|
||||
# '__title' does not contain extra words such as sub-site name, "Video" etc.
|
||||
title = compat_urllib_parse_unquote_plus(
|
||||
self._search_regex(r"__title\s*=\s*'([^']+)'", webpage, 'title', default=None) or
|
||||
self._og_search_title(webpage))
|
||||
|
||||
filename = self._search_regex(
|
||||
r"__filename='([^']+)'", webpage, 'video filename')
|
||||
video_url = 'http://bitcast-b.bitgravity.com/ndtvod/23372/ndtv/%s' % filename
|
||||
r"(?:__)?filename\s*[:=]\s*'([^']+)'", webpage, 'video filename')
|
||||
# in "movies" sub-site pages, filename is URL
|
||||
video_url = urljoin('https://ndtvod.bc-ssl.cdn.bitgravity.com/23372/ndtv/', filename.lstrip('/'))
|
||||
|
||||
duration = int_or_none(self._search_regex(
|
||||
r"__duration='([^']+)'", webpage, 'duration', fatal=False))
|
||||
# "doctor" sub-site has MM:SS format
|
||||
duration = parse_duration(self._search_regex(
|
||||
r"(?:__)?duration\s*[:=]\s*'([^']+)'", webpage, 'duration', fatal=False))
|
||||
|
||||
# "sports", "doctor", "swirlster" sub-sites don't have 'publish-date'
|
||||
upload_date = unified_strdate(self._html_search_meta(
|
||||
'publish-date', webpage, 'upload date', fatal=False))
|
||||
'publish-date', webpage, 'upload date', default=None) or self._html_search_meta(
|
||||
'uploadDate', webpage, 'upload date', default=None) or self._search_regex(
|
||||
r'datePublished"\s*:\s*"([^"]+)"', webpage, 'upload date', fatal=False))
|
||||
|
||||
description = remove_end(self._og_search_description(webpage), ' (Read more)')
|
||||
|
||||
|
@@ -75,7 +75,7 @@ class NickIE(MTVServicesInfoExtractor):
|
||||
|
||||
class NickDeIE(MTVServicesInfoExtractor):
|
||||
IE_NAME = 'nick.de'
|
||||
_VALID_URL = r'https?://(?:www\.)?(?P<host>nick\.(?:de|com\.pl)|nickelodeon\.(?:nl|at))/[^/]+/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
||||
_VALID_URL = r'https?://(?:www\.)?(?P<host>nick\.(?:de|com\.pl|ch)|nickelodeon\.(?:nl|be|at|dk|no|se))/[^/]+/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
||||
_TESTS = [{
|
||||
'url': 'http://www.nick.de/playlist/3773-top-videos/videos/episode/17306-zu-wasser-und-zu-land-rauchende-erdnusse',
|
||||
'only_matching': True,
|
||||
@@ -91,6 +91,21 @@ class NickDeIE(MTVServicesInfoExtractor):
|
||||
}, {
|
||||
'url': 'http://www.nick.com.pl/seriale/474-spongebob-kanciastoporty/wideo/17412-teatr-to-jest-to-rodeo-oszolom',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.nickelodeon.no/program/2626-bulderhuset/videoer/90947-femteklasse-veronica-vs-vanzilla',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.nickelodeon.dk/serier/2626-hojs-hus/videoer/761-tissepause',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.nickelodeon.se/serier/2626-lugn-i-stormen/videos/998-',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.nick.ch/shows/2304-adventure-time-abenteuerzeit-mit-finn-und-jake',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.nickelodeon.be/afspeellijst/4530-top-videos/videos/episode/73917-inval-broodschapper-lariekoek-arie',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _extract_mrss_url(self, webpage, host):
|
||||
@@ -132,13 +147,28 @@ class NickNightIE(NickDeIE):
|
||||
|
||||
class NickRuIE(MTVServicesInfoExtractor):
|
||||
IE_NAME = 'nickelodeonru'
|
||||
_VALID_URL = r'https?://(?:www\.)nickelodeon\.ru/(?:playlist|shows|videos)/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
||||
_VALID_URL = r'https?://(?:www\.)nickelodeon\.(?:ru|fr|es|pt|ro|hu)/[^/]+/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
||||
_TESTS = [{
|
||||
'url': 'http://www.nickelodeon.ru/shows/henrydanger/videos/episodes/3-sezon-15-seriya-licenziya-na-polyot/pmomfb#playlist/7airc6',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.nickelodeon.ru/videos/smotri-na-nickelodeon-v-iyule/g9hvh7',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.nickelodeon.fr/programmes/bob-l-eponge/videos/le-marathon-de-booh-kini-bottom-mardi-31-octobre/nfn7z0',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.nickelodeon.es/videos/nickelodeon-consejos-tortitas/f7w7xy',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.nickelodeon.pt/series/spongebob-squarepants/videos/a-bolha-de-tinta-gigante/xutq1b',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.nickelodeon.ro/emisiuni/shimmer-si-shine/video/nahal-din-bomboane/uw5u2k',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'http://www.nickelodeon.hu/musorok/spongyabob-kockanadrag/videok/episodes/buborekfujas-az-elszakadt-nadrag/q57iob#playlist/k6te4y',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
@@ -8,36 +8,49 @@ from .common import InfoExtractor
|
||||
|
||||
class SoundgasmIE(InfoExtractor):
|
||||
IE_NAME = 'soundgasm'
|
||||
_VALID_URL = r'https?://(?:www\.)?soundgasm\.net/u/(?P<user>[0-9a-zA-Z_\-]+)/(?P<title>[0-9a-zA-Z_\-]+)'
|
||||
_VALID_URL = r'https?://(?:www\.)?soundgasm\.net/u/(?P<user>[0-9a-zA-Z_-]+)/(?P<display_id>[0-9a-zA-Z_-]+)'
|
||||
_TEST = {
|
||||
'url': 'http://soundgasm.net/u/ytdl/Piano-sample',
|
||||
'md5': '010082a2c802c5275bb00030743e75ad',
|
||||
'info_dict': {
|
||||
'id': '88abd86ea000cafe98f96321b23cc1206cbcbcc9',
|
||||
'ext': 'm4a',
|
||||
'title': 'ytdl_Piano-sample',
|
||||
'description': 'Royalty Free Sample Music'
|
||||
'title': 'Piano sample',
|
||||
'description': 'Royalty Free Sample Music',
|
||||
'uploader': 'ytdl',
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
display_id = mobj.group('title')
|
||||
audio_title = mobj.group('user') + '_' + mobj.group('title')
|
||||
display_id = mobj.group('display_id')
|
||||
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
|
||||
audio_url = self._html_search_regex(
|
||||
r'(?s)m4a\:\s"([^"]+)"', webpage, 'audio URL')
|
||||
audio_id = re.split(r'\/|\.', audio_url)[-2]
|
||||
r'(?s)m4a\s*:\s*(["\'])(?P<url>(?:(?!\1).)+)\1', webpage,
|
||||
'audio URL', group='url')
|
||||
|
||||
title = self._search_regex(
|
||||
r'<div[^>]+\bclass=["\']jp-title[^>]+>([^<]+)',
|
||||
webpage, 'title', default=display_id)
|
||||
|
||||
description = self._html_search_regex(
|
||||
r'(?s)<li>Description:\s(.*?)<\/li>', webpage, 'description',
|
||||
fatal=False)
|
||||
(r'(?s)<div[^>]+\bclass=["\']jp-description[^>]+>(.+?)</div>',
|
||||
r'(?s)<li>Description:\s(.*?)<\/li>'),
|
||||
webpage, 'description', fatal=False)
|
||||
|
||||
audio_id = self._search_regex(
|
||||
r'/([^/]+)\.m4a', audio_url, 'audio id', default=display_id)
|
||||
|
||||
return {
|
||||
'id': audio_id,
|
||||
'display_id': display_id,
|
||||
'url': audio_url,
|
||||
'title': audio_title,
|
||||
'description': description
|
||||
'vcodec': 'none',
|
||||
'title': title,
|
||||
'description': description,
|
||||
'uploader': mobj.group('user'),
|
||||
}
|
||||
|
||||
|
||||
|
@@ -609,7 +609,7 @@ class TwitchClipsIE(InfoExtractor):
|
||||
r'(?s)clipInfo\s*=\s*({.+?});', webpage, 'clip info'),
|
||||
video_id, transform_source=js_to_json)
|
||||
|
||||
title = clip.get('channel_title') or self._og_search_title(webpage)
|
||||
title = clip.get('title') or clip.get('channel_title') or self._og_search_title(webpage)
|
||||
|
||||
formats = [{
|
||||
'url': option['source'],
|
||||
|
@@ -412,7 +412,7 @@ class VimeoIE(VimeoBaseInfoExtractor):
|
||||
urls = []
|
||||
# Look for embedded (iframe) Vimeo player
|
||||
for mobj in re.finditer(
|
||||
r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//player\.vimeo\.com/video/.+?)\1',
|
||||
r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//player\.vimeo\.com/video/\d+.*?)\1',
|
||||
webpage):
|
||||
urls.append(VimeoIE._smuggle_referrer(unescapeHTML(mobj.group('url')), url))
|
||||
PLAIN_EMBED_RE = (
|
||||
|
202
youtube_dl/extractor/younow.py
Normal file
202
youtube_dl/extractor/younow.py
Normal file
@@ -0,0 +1,202 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import itertools
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..compat import compat_str
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
int_or_none,
|
||||
try_get,
|
||||
)
|
||||
|
||||
CDN_API_BASE = 'https://cdn.younow.com/php/api'
|
||||
MOMENT_URL_FORMAT = '%s/moment/fetch/id=%%s' % CDN_API_BASE
|
||||
|
||||
|
||||
class YouNowLiveIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?younow\.com/(?P<id>[^/?#&]+)'
|
||||
_TEST = {
|
||||
'url': 'https://www.younow.com/AmandaPadeezy',
|
||||
'info_dict': {
|
||||
'id': 'AmandaPadeezy',
|
||||
'ext': 'mp4',
|
||||
'is_live': True,
|
||||
'title': 'March 26, 2017',
|
||||
'thumbnail': r're:^https?://.*\.jpg$',
|
||||
'tags': ['girls'],
|
||||
'categories': ['girls'],
|
||||
'uploader': 'AmandaPadeezy',
|
||||
'uploader_id': '6716501',
|
||||
'uploader_url': 'https://www.younow.com/AmandaPadeezy',
|
||||
'creator': 'AmandaPadeezy',
|
||||
},
|
||||
'skip': True,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def suitable(cls, url):
|
||||
return (False
|
||||
if YouNowChannelIE.suitable(url) or YouNowMomentIE.suitable(url)
|
||||
else super(YouNowLiveIE, cls).suitable(url))
|
||||
|
||||
def _real_extract(self, url):
|
||||
username = self._match_id(url)
|
||||
|
||||
data = self._download_json(
|
||||
'https://api.younow.com/php/api/broadcast/info/curId=0/user=%s'
|
||||
% username, username)
|
||||
|
||||
if data.get('errorCode') != 0:
|
||||
raise ExtractorError(data['errorMsg'], expected=True)
|
||||
|
||||
uploader = try_get(
|
||||
data, lambda x: x['user']['profileUrlString'],
|
||||
compat_str) or username
|
||||
|
||||
return {
|
||||
'id': uploader,
|
||||
'is_live': True,
|
||||
'title': self._live_title(uploader),
|
||||
'thumbnail': data.get('awsUrl'),
|
||||
'tags': data.get('tags'),
|
||||
'categories': data.get('tags'),
|
||||
'uploader': uploader,
|
||||
'uploader_id': data.get('userId'),
|
||||
'uploader_url': 'https://www.younow.com/%s' % username,
|
||||
'creator': uploader,
|
||||
'view_count': int_or_none(data.get('viewers')),
|
||||
'like_count': int_or_none(data.get('likes')),
|
||||
'formats': [{
|
||||
'url': '%s/broadcast/videoPath/hls=1/broadcastId=%s/channelId=%s'
|
||||
% (CDN_API_BASE, data['broadcastId'], data['userId']),
|
||||
'ext': 'mp4',
|
||||
'protocol': 'm3u8',
|
||||
}],
|
||||
}
|
||||
|
||||
|
||||
def _extract_moment(item, fatal=True):
|
||||
moment_id = item.get('momentId')
|
||||
if not moment_id:
|
||||
if not fatal:
|
||||
return
|
||||
raise ExtractorError('Unable to extract moment id')
|
||||
|
||||
moment_id = compat_str(moment_id)
|
||||
|
||||
title = item.get('text')
|
||||
if not title:
|
||||
title = 'YouNow %s' % (
|
||||
item.get('momentType') or item.get('titleType') or 'moment')
|
||||
|
||||
uploader = try_get(item, lambda x: x['owner']['name'], compat_str)
|
||||
uploader_id = try_get(item, lambda x: x['owner']['userId'])
|
||||
uploader_url = 'https://www.younow.com/%s' % uploader if uploader else None
|
||||
|
||||
entry = {
|
||||
'extractor_key': 'YouNowMoment',
|
||||
'id': moment_id,
|
||||
'title': title,
|
||||
'view_count': int_or_none(item.get('views')),
|
||||
'like_count': int_or_none(item.get('likes')),
|
||||
'timestamp': int_or_none(item.get('created')),
|
||||
'creator': uploader,
|
||||
'uploader': uploader,
|
||||
'uploader_id': uploader_id,
|
||||
'uploader_url': uploader_url,
|
||||
'formats': [{
|
||||
'url': 'https://hls.younow.com/momentsplaylists/live/%s/%s.m3u8'
|
||||
% (moment_id, moment_id),
|
||||
'ext': 'mp4',
|
||||
'protocol': 'm3u8_native',
|
||||
}],
|
||||
}
|
||||
|
||||
return entry
|
||||
|
||||
|
||||
class YouNowChannelIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?younow\.com/(?P<id>[^/]+)/channel'
|
||||
_TEST = {
|
||||
'url': 'https://www.younow.com/its_Kateee_/channel',
|
||||
'info_dict': {
|
||||
'id': '14629760',
|
||||
'title': 'its_Kateee_ moments'
|
||||
},
|
||||
'playlist_mincount': 8,
|
||||
}
|
||||
|
||||
def _entries(self, username, channel_id):
|
||||
created_before = 0
|
||||
for page_num in itertools.count(1):
|
||||
if created_before is None:
|
||||
break
|
||||
info = self._download_json(
|
||||
'%s/moment/profile/channelId=%s/createdBefore=%d/records=20'
|
||||
% (CDN_API_BASE, channel_id, created_before), username,
|
||||
note='Downloading moments page %d' % page_num)
|
||||
items = info.get('items')
|
||||
if not items or not isinstance(items, list):
|
||||
break
|
||||
for item in items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
item_type = item.get('type')
|
||||
if item_type == 'moment':
|
||||
entry = _extract_moment(item, fatal=False)
|
||||
if entry:
|
||||
yield entry
|
||||
elif item_type == 'collection':
|
||||
moments = item.get('momentsIds')
|
||||
if isinstance(moments, list):
|
||||
for moment_id in moments:
|
||||
m = self._download_json(
|
||||
MOMENT_URL_FORMAT % moment_id, username,
|
||||
note='Downloading %s moment JSON' % moment_id,
|
||||
fatal=False)
|
||||
if m and isinstance(m, dict) and m.get('item'):
|
||||
entry = _extract_moment(m['item'])
|
||||
if entry:
|
||||
yield entry
|
||||
created_before = int_or_none(item.get('created'))
|
||||
|
||||
def _real_extract(self, url):
|
||||
username = self._match_id(url)
|
||||
channel_id = compat_str(self._download_json(
|
||||
'https://api.younow.com/php/api/broadcast/info/curId=0/user=%s'
|
||||
% username, username, note='Downloading user information')['userId'])
|
||||
return self.playlist_result(
|
||||
self._entries(username, channel_id), channel_id,
|
||||
'%s moments' % username)
|
||||
|
||||
|
||||
class YouNowMomentIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?younow\.com/[^/]+/(?P<id>[^/?#&]+)'
|
||||
_TEST = {
|
||||
'url': 'https://www.younow.com/GABO.../20712117/36319236/3b316doc/m',
|
||||
'md5': 'a30c70eadb9fb39a1aa3c8c0d22a0807',
|
||||
'info_dict': {
|
||||
'id': '20712117',
|
||||
'ext': 'mp4',
|
||||
'title': 'YouNow capture',
|
||||
'view_count': int,
|
||||
'like_count': int,
|
||||
'timestamp': 1490432040,
|
||||
'upload_date': '20170325',
|
||||
'uploader': 'GABO...',
|
||||
'uploader_id': 35917228,
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def suitable(cls, url):
|
||||
return (False
|
||||
if YouNowChannelIE.suitable(url)
|
||||
else super(YouNowMomentIE, cls).suitable(url))
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
item = self._download_json(MOMENT_URL_FORMAT % video_id, video_id)
|
||||
return _extract_moment(item['item'])
|
@@ -1391,7 +1391,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
)
|
||||
(["\'])
|
||||
(?P<url>(?:https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/
|
||||
(?:embed|v|p)/.+?)
|
||||
(?:embed|v|p)/[0-9A-Za-z_-]{11}.*?)
|
||||
\1''', webpage)]
|
||||
|
||||
# lazyYT YouTube embed
|
||||
|
@@ -1835,10 +1835,20 @@ def parse_duration(s):
|
||||
days, hours, mins, secs, ms = m.groups()
|
||||
else:
|
||||
m = re.match(
|
||||
r'''(?ix)(?:P?T)?
|
||||
r'''(?ix)(?:P?
|
||||
(?:
|
||||
[0-9]+\s*y(?:ears?)?\s*
|
||||
)?
|
||||
(?:
|
||||
[0-9]+\s*m(?:onths?)?\s*
|
||||
)?
|
||||
(?:
|
||||
[0-9]+\s*w(?:eeks?)?\s*
|
||||
)?
|
||||
(?:
|
||||
(?P<days>[0-9]+)\s*d(?:ays?)?\s*
|
||||
)?
|
||||
T)?
|
||||
(?:
|
||||
(?P<hours>[0-9]+)\s*h(?:ours?)?\s*
|
||||
)?
|
||||
|
@@ -1,3 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__version__ = '2017.10.20'
|
||||
__version__ = '2017.10.29'
|
||||
|
Reference in New Issue
Block a user