Compare commits
55 Commits
2014.12.10
...
2014.12.12
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3b0bec8d11 | ||
![]() |
412c617d0f | ||
![]() |
751536f5c8 | ||
![]() |
025f30ba38 | ||
![]() |
0d2fb1d193 | ||
![]() |
82b34105d3 | ||
![]() |
73aeb2dc56 | ||
![]() |
c6973bd412 | ||
![]() |
f8780e6d11 | ||
![]() |
e2f89ec7aa | ||
![]() |
62651c556a | ||
![]() |
bf94e38d3d | ||
![]() |
4f97852316 | ||
![]() |
16040f46d6 | ||
![]() |
d068ba24f3 | ||
![]() |
f5e43bc695 | ||
![]() |
6a5308ab49 | ||
![]() |
63e0f29564 | ||
![]() |
42bdd9d051 | ||
![]() |
4e40de6e2a | ||
![]() |
0fa2b899d1 | ||
![]() |
f17e4c9c28 | ||
![]() |
807962f4a1 | ||
![]() |
9c1aa1d668 | ||
![]() |
69f491f14e | ||
![]() |
cb007f47c1 | ||
![]() |
9abd500a74 | ||
![]() |
cf68bcaeff | ||
![]() |
cbe2bd914d | ||
![]() |
75111274ed | ||
![]() |
624dcebff6 | ||
![]() |
9684f17cde | ||
![]() |
e52a40abf7 | ||
![]() |
0daa05961b | ||
![]() |
158731f83e | ||
![]() |
24270b0301 | ||
![]() |
3c1b81b957 | ||
![]() |
45c24df512 | ||
![]() |
bf671b605e | ||
![]() |
09c82fbc9a | ||
![]() |
3bca0409fe | ||
![]() |
d6f78a354d | ||
![]() |
e0b9d47387 | ||
![]() |
f8795e102b | ||
![]() |
4bb4a18876 | ||
![]() |
8560c61842 | ||
![]() |
a81bbebf44 | ||
![]() |
72e3ffeb74 | ||
![]() |
2fc9f2b41d | ||
![]() |
ce36339575 | ||
![]() |
684712076f | ||
![]() |
6ac4e8065a | ||
![]() |
e638e83662 | ||
![]() |
d958fa9ff9 | ||
![]() |
ebb6419960 |
2
Makefile
2
Makefile
@@ -1,7 +1,7 @@
|
|||||||
all: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtube-dl.fish
|
all: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtube-dl.fish
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf youtube-dl.1.temp.md youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz youtube-dl.zsh youtube-dl.fish *.dump *.part
|
rm -rf youtube-dl.1.temp.md youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz youtube-dl.zsh youtube-dl.fish *.dump *.part *.info.json
|
||||||
|
|
||||||
cleanall: clean
|
cleanall: clean
|
||||||
rm -f youtube-dl youtube-dl.exe
|
rm -f youtube-dl youtube-dl.exe
|
||||||
|
@@ -144,6 +144,9 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(unified_strdate('2012/10/11 01:56:38 +0000'), '20121011')
|
self.assertEqual(unified_strdate('2012/10/11 01:56:38 +0000'), '20121011')
|
||||||
self.assertEqual(unified_strdate('1968-12-10'), '19681210')
|
self.assertEqual(unified_strdate('1968-12-10'), '19681210')
|
||||||
self.assertEqual(unified_strdate('28/01/2014 21:00:00 +0100'), '20140128')
|
self.assertEqual(unified_strdate('28/01/2014 21:00:00 +0100'), '20140128')
|
||||||
|
self.assertEqual(
|
||||||
|
unified_strdate('11/26/2014 11:30:00 AM PST', day_first=False),
|
||||||
|
'20141126')
|
||||||
|
|
||||||
def test_find_xpath_attr(self):
|
def test_find_xpath_attr(self):
|
||||||
testxml = '''<root>
|
testxml = '''<root>
|
||||||
|
@@ -622,23 +622,15 @@ class YoutubeDL(object):
|
|||||||
ie_result['url'], ie_key=ie_result.get('ie_key'),
|
ie_result['url'], ie_key=ie_result.get('ie_key'),
|
||||||
extra_info=extra_info, download=False, process=False)
|
extra_info=extra_info, download=False, process=False)
|
||||||
|
|
||||||
def make_result(embedded_info):
|
force_properties = dict(
|
||||||
new_result = ie_result.copy()
|
(k, v) for k, v in ie_result.items() if v is not None)
|
||||||
for f in ('_type', 'url', 'ext', 'player_url', 'formats',
|
for f in ('_type', 'url'):
|
||||||
'entries', 'ie_key', 'duration',
|
if f in force_properties:
|
||||||
'subtitles', 'annotations', 'format',
|
del force_properties[f]
|
||||||
'thumbnail', 'thumbnails'):
|
new_result = info.copy()
|
||||||
if f in new_result:
|
new_result.update(force_properties)
|
||||||
del new_result[f]
|
|
||||||
if f in embedded_info:
|
|
||||||
new_result[f] = embedded_info[f]
|
|
||||||
return new_result
|
|
||||||
new_result = make_result(info)
|
|
||||||
|
|
||||||
assert new_result.get('_type') != 'url_transparent'
|
assert new_result.get('_type') != 'url_transparent'
|
||||||
if new_result.get('_type') == 'compat_list':
|
|
||||||
new_result['entries'] = [
|
|
||||||
make_result(e) for e in new_result['entries']]
|
|
||||||
|
|
||||||
return self.process_ie_result(
|
return self.process_ie_result(
|
||||||
new_result, download=download, extra_info=extra_info)
|
new_result, download=download, extra_info=extra_info)
|
||||||
|
@@ -297,7 +297,9 @@ else:
|
|||||||
|
|
||||||
# Old 2.6 and 2.7 releases require kwargs to be bytes
|
# Old 2.6 and 2.7 releases require kwargs to be bytes
|
||||||
try:
|
try:
|
||||||
(lambda x: x)(**{'x': 0})
|
def _testfunc(x):
|
||||||
|
pass
|
||||||
|
_testfunc(**{'x': 0})
|
||||||
except TypeError:
|
except TypeError:
|
||||||
def compat_kwargs(kwargs):
|
def compat_kwargs(kwargs):
|
||||||
return dict((bytes(k), v) for k, v in kwargs.items())
|
return dict((bytes(k), v) for k, v in kwargs.items())
|
||||||
|
@@ -51,7 +51,7 @@ from .cbsnews import CBSNewsIE
|
|||||||
from .ceskatelevize import CeskaTelevizeIE
|
from .ceskatelevize import CeskaTelevizeIE
|
||||||
from .channel9 import Channel9IE
|
from .channel9 import Channel9IE
|
||||||
from .chilloutzone import ChilloutzoneIE
|
from .chilloutzone import ChilloutzoneIE
|
||||||
from .cinemassacre import CinemassacreIE
|
from .cinchcast import CinchcastIE
|
||||||
from .clipfish import ClipfishIE
|
from .clipfish import ClipfishIE
|
||||||
from .cliphunter import CliphunterIE
|
from .cliphunter import CliphunterIE
|
||||||
from .clipsyndicate import ClipsyndicateIE
|
from .clipsyndicate import ClipsyndicateIE
|
||||||
@@ -336,6 +336,7 @@ from .savefrom import SaveFromIE
|
|||||||
from .sbs import SBSIE
|
from .sbs import SBSIE
|
||||||
from .scivee import SciVeeIE
|
from .scivee import SciVeeIE
|
||||||
from .screencast import ScreencastIE
|
from .screencast import ScreencastIE
|
||||||
|
from .screenwavemedia import CinemassacreIE, ScreenwaveMediaIE, TeamFourIE
|
||||||
from .servingsys import ServingSysIE
|
from .servingsys import ServingSysIE
|
||||||
from .sexu import SexuIE
|
from .sexu import SexuIE
|
||||||
from .sexykarma import SexyKarmaIE
|
from .sexykarma import SexyKarmaIE
|
||||||
@@ -526,7 +527,7 @@ from .youtube import (
|
|||||||
YoutubeUserIE,
|
YoutubeUserIE,
|
||||||
YoutubeWatchLaterIE,
|
YoutubeWatchLaterIE,
|
||||||
)
|
)
|
||||||
from .zdf import ZDFIE
|
from .zdf import ZDFIE, ZDFChannelIE
|
||||||
from .zingmp3 import (
|
from .zingmp3 import (
|
||||||
ZingMp3SongIE,
|
ZingMp3SongIE,
|
||||||
ZingMp3AlbumIE,
|
ZingMp3AlbumIE,
|
||||||
|
@@ -10,15 +10,15 @@ from ..utils import url_basename
|
|||||||
class BehindKinkIE(InfoExtractor):
|
class BehindKinkIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://(?:www\.)?behindkink\.com/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/(?P<id>[^/#?_]+)'
|
_VALID_URL = r'http://(?:www\.)?behindkink\.com/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/(?P<id>[^/#?_]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.behindkink.com/2014/08/14/ab1576-performers-voice-finally-heard-the-bill-is-killed/',
|
'url': 'http://www.behindkink.com/2014/12/05/what-are-you-passionate-about-marley-blaze/',
|
||||||
'md5': '41ad01222b8442089a55528fec43ec01',
|
'md5': '507b57d8fdcd75a41a9a7bdb7989c762',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '36370',
|
'id': '37127',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'AB1576 - PERFORMERS VOICE FINALLY HEARD - THE BILL IS KILLED!',
|
'title': 'What are you passionate about – Marley Blaze',
|
||||||
'description': 'The adult industry voice was finally heard as Assembly Bill 1576 remained\xa0 in suspense today at the Senate Appropriations Hearing. AB1576 was, among other industry damaging issues, a condom mandate...',
|
'description': 'md5:aee8e9611b4ff70186f752975d9b94b4',
|
||||||
'upload_date': '20140814',
|
'upload_date': '20141205',
|
||||||
'thumbnail': 'http://www.behindkink.com/wp-content/uploads/2014/08/36370_AB1576_Win.jpg',
|
'thumbnail': 'http://www.behindkink.com/wp-content/uploads/2014/12/blaze-1.jpg',
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,26 +26,19 @@ class BehindKinkIE(InfoExtractor):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
display_id = mobj.group('id')
|
display_id = mobj.group('id')
|
||||||
year = mobj.group('year')
|
|
||||||
month = mobj.group('month')
|
|
||||||
day = mobj.group('day')
|
|
||||||
upload_date = year + month + day
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
video_url = self._search_regex(
|
video_url = self._search_regex(
|
||||||
r"'file':\s*'([^']+)'",
|
r'<source src="([^"]+)"', webpage, 'video URL')
|
||||||
webpage, 'URL base')
|
video_id = url_basename(video_url).split('_')[0]
|
||||||
|
upload_date = mobj.group('year') + mobj.group('month') + mobj.group('day')
|
||||||
video_id = url_basename(video_url)
|
|
||||||
video_id = video_id.split('_')[0]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': self._og_search_title(webpage),
|
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': self._og_search_title(webpage),
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
'description': self._og_search_description(webpage),
|
'description': self._og_search_description(webpage),
|
||||||
'upload_date': upload_date,
|
'upload_date': upload_date,
|
||||||
|
@@ -236,16 +236,17 @@ class Channel9IE(InfoExtractor):
|
|||||||
if contents is None:
|
if contents is None:
|
||||||
return contents
|
return contents
|
||||||
|
|
||||||
session_meta = {'session_code': self._extract_session_code(html),
|
session_meta = {
|
||||||
'session_day': self._extract_session_day(html),
|
'session_code': self._extract_session_code(html),
|
||||||
'session_room': self._extract_session_room(html),
|
'session_day': self._extract_session_day(html),
|
||||||
'session_speakers': self._extract_session_speakers(html),
|
'session_room': self._extract_session_room(html),
|
||||||
}
|
'session_speakers': self._extract_session_speakers(html),
|
||||||
|
}
|
||||||
|
|
||||||
for content in contents:
|
for content in contents:
|
||||||
content.update(session_meta)
|
content.update(session_meta)
|
||||||
|
|
||||||
return contents
|
return self.playlist_result(contents)
|
||||||
|
|
||||||
def _extract_list(self, content_path):
|
def _extract_list(self, content_path):
|
||||||
rss = self._download_xml(self._RSS_URL % content_path, content_path, 'Downloading RSS')
|
rss = self._download_xml(self._RSS_URL % content_path, content_path, 'Downloading RSS')
|
||||||
|
52
youtube_dl/extractor/cinchcast.py
Normal file
52
youtube_dl/extractor/cinchcast.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
unified_strdate,
|
||||||
|
xpath_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CinchcastIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://player\.cinchcast\.com/.*?assetId=(?P<id>[0-9]+)'
|
||||||
|
_TEST = {
|
||||||
|
# Actual test is run in generic, look for undergroundwellness
|
||||||
|
'url': 'http://player.cinchcast.com/?platformId=1&assetType=single&assetId=7141703',
|
||||||
|
'only_matching': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
doc = self._download_xml(
|
||||||
|
'http://www.blogtalkradio.com/playerasset/mrss?assetType=single&assetId=%s' % video_id,
|
||||||
|
video_id)
|
||||||
|
|
||||||
|
item = doc.find('.//item')
|
||||||
|
title = xpath_text(item, './title', fatal=True)
|
||||||
|
date_str = xpath_text(
|
||||||
|
item, './{http://developer.longtailvideo.com/trac/}date')
|
||||||
|
upload_date = unified_strdate(date_str, day_first=False)
|
||||||
|
# duration is present but wrong
|
||||||
|
formats = []
|
||||||
|
formats.append({
|
||||||
|
'format_id': 'main',
|
||||||
|
'url': item.find(
|
||||||
|
'./{http://search.yahoo.com/mrss/}content').attrib['url'],
|
||||||
|
})
|
||||||
|
backup_url = xpath_text(
|
||||||
|
item, './{http://developer.longtailvideo.com/trac/}backupContent')
|
||||||
|
if backup_url:
|
||||||
|
formats.append({
|
||||||
|
'preference': 2, # seems to be more reliable
|
||||||
|
'format_id': 'backup',
|
||||||
|
'url': backup_url,
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@@ -15,23 +15,24 @@ class CNETIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://(?:www\.)?cnet\.com/videos/(?P<id>[^/]+)/'
|
_VALID_URL = r'https?://(?:www\.)?cnet\.com/videos/(?P<id>[^/]+)/'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.cnet.com/videos/hands-on-with-microsofts-windows-8-1-update/',
|
'url': 'http://www.cnet.com/videos/hands-on-with-microsofts-windows-8-1-update/',
|
||||||
'md5': '041233212a0d06b179c87cbcca1577b8',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '56f4ea68-bd21-4852-b08c-4de5b8354c60',
|
'id': '56f4ea68-bd21-4852-b08c-4de5b8354c60',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'title': 'Hands-on with Microsoft Windows 8.1 Update',
|
'title': 'Hands-on with Microsoft Windows 8.1 Update',
|
||||||
'description': 'The new update to the Windows 8 OS brings improved performance for mouse and keyboard users.',
|
'description': 'The new update to the Windows 8 OS brings improved performance for mouse and keyboard users.',
|
||||||
'thumbnail': 're:^http://.*/flmswindows8.jpg$',
|
'thumbnail': 're:^http://.*/flmswindows8.jpg$',
|
||||||
'uploader_id': 'sarah.mitroff@cbsinteractive.com',
|
'uploader_id': '6085384d-619e-11e3-b231-14feb5ca9861',
|
||||||
'uploader': 'Sarah Mitroff',
|
'uploader': 'Sarah Mitroff',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': 'requires rtmpdump',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
display_id = self._match_id(url)
|
||||||
display_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
data_json = self._html_search_regex(
|
data_json = self._html_search_regex(
|
||||||
r"<div class=\"cnetVideoPlayer\"\s+.*?data-cnet-video-options='([^']+)'",
|
r"<div class=\"cnetVideoPlayer\"\s+.*?data-cnet-video-options='([^']+)'",
|
||||||
webpage, 'data json')
|
webpage, 'data json')
|
||||||
@@ -42,37 +43,31 @@ class CNETIE(InfoExtractor):
|
|||||||
if not vdata:
|
if not vdata:
|
||||||
raise ExtractorError('Cannot find video data')
|
raise ExtractorError('Cannot find video data')
|
||||||
|
|
||||||
|
mpx_account = data['config']['players']['default']['mpx_account']
|
||||||
|
vid = vdata['files']['rtmp']
|
||||||
|
tp_link = 'http://link.theplatform.com/s/%s/%s' % (mpx_account, vid)
|
||||||
|
|
||||||
video_id = vdata['id']
|
video_id = vdata['id']
|
||||||
title = vdata.get('headline')
|
title = vdata.get('headline')
|
||||||
if title is None:
|
if title is None:
|
||||||
title = vdata.get('title')
|
title = vdata.get('title')
|
||||||
if title is None:
|
if title is None:
|
||||||
raise ExtractorError('Cannot find title!')
|
raise ExtractorError('Cannot find title!')
|
||||||
description = vdata.get('dek')
|
|
||||||
thumbnail = vdata.get('image', {}).get('path')
|
thumbnail = vdata.get('image', {}).get('path')
|
||||||
author = vdata.get('author')
|
author = vdata.get('author')
|
||||||
if author:
|
if author:
|
||||||
uploader = '%s %s' % (author['firstName'], author['lastName'])
|
uploader = '%s %s' % (author['firstName'], author['lastName'])
|
||||||
uploader_id = author.get('email')
|
uploader_id = author.get('id')
|
||||||
else:
|
else:
|
||||||
uploader = None
|
uploader = None
|
||||||
uploader_id = None
|
uploader_id = None
|
||||||
|
|
||||||
formats = [{
|
|
||||||
'format_id': '%s-%s-%s' % (
|
|
||||||
f['type'], f['format'],
|
|
||||||
int_or_none(f.get('bitrate'), 1000, default='')),
|
|
||||||
'url': f['uri'],
|
|
||||||
'tbr': int_or_none(f.get('bitrate'), 1000),
|
|
||||||
} for f in vdata['files']['data']]
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': tp_link,
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'formats': formats,
|
|
||||||
'description': description,
|
|
||||||
'uploader': uploader,
|
'uploader': uploader,
|
||||||
'uploader_id': uploader_id,
|
'uploader_id': uploader_id,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
|
@@ -118,6 +118,7 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
The following fields are optional:
|
The following fields are optional:
|
||||||
|
|
||||||
|
alt_title: A secondary title of the video.
|
||||||
display_id An alternative identifier for the video, not necessarily
|
display_id An alternative identifier for the video, not necessarily
|
||||||
unique, but available before title. Typically, id is
|
unique, but available before title. Typically, id is
|
||||||
something like "4234987", title "Dancing naked mole rats",
|
something like "4234987", title "Dancing naked mole rats",
|
||||||
@@ -129,7 +130,7 @@ class InfoExtractor(object):
|
|||||||
* "resolution" (optional, string "{width}x{height"},
|
* "resolution" (optional, string "{width}x{height"},
|
||||||
deprecated)
|
deprecated)
|
||||||
thumbnail: Full URL to a video thumbnail image.
|
thumbnail: Full URL to a video thumbnail image.
|
||||||
description: One-line video description.
|
description: Full video description.
|
||||||
uploader: Full name of the video uploader.
|
uploader: Full name of the video uploader.
|
||||||
timestamp: UNIX timestamp of the moment the video became available.
|
timestamp: UNIX timestamp of the moment the video became available.
|
||||||
upload_date: Video upload date (YYYYMMDD).
|
upload_date: Video upload date (YYYYMMDD).
|
||||||
@@ -391,6 +392,10 @@ class InfoExtractor(object):
|
|||||||
url_or_request, video_id, note, errnote, fatal=fatal)
|
url_or_request, video_id, note, errnote, fatal=fatal)
|
||||||
if (not fatal) and json_string is False:
|
if (not fatal) and json_string is False:
|
||||||
return None
|
return None
|
||||||
|
return self._parse_json(
|
||||||
|
json_string, video_id, transform_source=transform_source, fatal=fatal)
|
||||||
|
|
||||||
|
def _parse_json(self, json_string, video_id, transform_source=None, fatal=True):
|
||||||
if transform_source:
|
if transform_source:
|
||||||
json_string = transform_source(json_string)
|
json_string = transform_source(json_string)
|
||||||
try:
|
try:
|
||||||
|
@@ -13,9 +13,10 @@ from ..compat import (
|
|||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
urlencode_postdata,
|
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
limit_length,
|
limit_length,
|
||||||
|
urlencode_postdata,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +37,6 @@ class FacebookIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '637842556329505',
|
'id': '637842556329505',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'duration': 38,
|
|
||||||
'title': 're:Did you know Kei Nishikori is the first Asian man to ever reach a Grand Slam',
|
'title': 're:Did you know Kei Nishikori is the first Asian man to ever reach a Grand Slam',
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
@@ -107,9 +107,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
self._login()
|
self._login()
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
url = 'https://www.facebook.com/video/video.php?v=%s' % video_id
|
url = 'https://www.facebook.com/video/video.php?v=%s' % video_id
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
@@ -149,6 +147,6 @@ class FacebookIE(InfoExtractor):
|
|||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': video_title,
|
'title': video_title,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'duration': int(video_data['video_duration']),
|
'duration': int_or_none(video_data.get('video_duration')),
|
||||||
'thumbnail': video_data['thumbnail_src'],
|
'thumbnail': video_data.get('thumbnail_src'),
|
||||||
}
|
}
|
||||||
|
@@ -467,8 +467,17 @@ class GenericIE(InfoExtractor):
|
|||||||
'expected_warnings': [
|
'expected_warnings': [
|
||||||
'URL could be a direct video link, returning it as such.'
|
'URL could be a direct video link, returning it as such.'
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
# Cinchcast embed
|
||||||
|
{
|
||||||
|
'url': 'http://undergroundwellness.com/podcasts/306-5-steps-to-permanent-gut-healing/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '7141703',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'upload_date': '20141126',
|
||||||
|
'title': 'Jack Tips: 5 Steps to Permanent Gut Healing',
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def report_following_redirect(self, new_url):
|
def report_following_redirect(self, new_url):
|
||||||
@@ -962,6 +971,13 @@ class GenericIE(InfoExtractor):
|
|||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result(mobj.group('url'), 'SBS')
|
return self.url_result(mobj.group('url'), 'SBS')
|
||||||
|
|
||||||
|
# Look for embedded Cinchcast player
|
||||||
|
mobj = re.search(
|
||||||
|
r'<iframe[^>]+?src=(["\'])(?P<url>https?://player\.cinchcast\.com/.+?)\1',
|
||||||
|
webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
return self.url_result(mobj.group('url'), 'Cinchcast')
|
||||||
|
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'<iframe[^>]+?src=(["\'])(?P<url>https?://m(?:lb)?\.mlb\.com/shared/video/embed/embed\.html\?.+?)\1',
|
r'<iframe[^>]+?src=(["\'])(?P<url>https?://m(?:lb)?\.mlb\.com/shared/video/embed/embed\.html\?.+?)\1',
|
||||||
webpage)
|
webpage)
|
||||||
|
@@ -17,7 +17,6 @@ class GoldenMoustacheIE(InfoExtractor):
|
|||||||
'title': 'Suricate - Le Poker',
|
'title': 'Suricate - Le Poker',
|
||||||
'description': 'md5:3d1f242f44f8c8cb0a106f1fd08e5dc9',
|
'description': 'md5:3d1f242f44f8c8cb0a106f1fd08e5dc9',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'view_count': int,
|
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.goldenmoustache.com/le-lab-tout-effacer-mc-fly-et-carlito-55249/',
|
'url': 'http://www.goldenmoustache.com/le-lab-tout-effacer-mc-fly-et-carlito-55249/',
|
||||||
@@ -28,7 +27,6 @@ class GoldenMoustacheIE(InfoExtractor):
|
|||||||
'title': 'Le LAB - Tout Effacer (Mc Fly et Carlito)',
|
'title': 'Le LAB - Tout Effacer (Mc Fly et Carlito)',
|
||||||
'description': 'md5:9b7fbf11023fb2250bd4b185e3de3b2a',
|
'description': 'md5:9b7fbf11023fb2250bd4b185e3de3b2a',
|
||||||
'thumbnail': 're:^https?://.*\.(?:png|jpg)$',
|
'thumbnail': 're:^https?://.*\.(?:png|jpg)$',
|
||||||
'view_count': int,
|
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@@ -42,9 +40,6 @@ class GoldenMoustacheIE(InfoExtractor):
|
|||||||
r'<title>(.*?)(?: - Golden Moustache)?</title>', webpage, 'title')
|
r'<title>(.*?)(?: - Golden Moustache)?</title>', webpage, 'title')
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
description = self._og_search_description(webpage)
|
description = self._og_search_description(webpage)
|
||||||
view_count = int_or_none(self._html_search_regex(
|
|
||||||
r'<strong>([0-9]+)</strong>\s*VUES</span>',
|
|
||||||
webpage, 'view count', fatal=False))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
@@ -53,5 +48,4 @@ class GoldenMoustacheIE(InfoExtractor):
|
|||||||
'title': title,
|
'title': title,
|
||||||
'description': description,
|
'description': description,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'view_count': view_count,
|
|
||||||
}
|
}
|
||||||
|
@@ -2,57 +2,52 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import (
|
||||||
|
compat_parse_qs,
|
||||||
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urlparse,
|
parse_duration,
|
||||||
ExtractorError,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GoshgayIE(InfoExtractor):
|
class GoshgayIE(InfoExtractor):
|
||||||
_VALID_URL = r'^(?:https?://)www.goshgay.com/video(?P<id>\d+?)($|/)'
|
_VALID_URL = r'https?://www\.goshgay\.com/video(?P<id>\d+?)($|/)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.goshgay.com/video4116282',
|
'url': 'http://www.goshgay.com/video299069/diesel_sfw_xxx_video',
|
||||||
'md5': '268b9f3c3229105c57859e166dd72b03',
|
'md5': '027fcc54459dff0feb0bc06a7aeda680',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '4116282',
|
'id': '299069',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'md5:089833a4790b5e103285a07337f245bf',
|
'title': 'DIESEL SFW XXX Video',
|
||||||
'thumbnail': 're:http://.*\.jpg',
|
'thumbnail': 're:^http://.*\.jpg$',
|
||||||
|
'duration': 79,
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
title = self._og_search_title(webpage)
|
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
title = self._html_search_regex(
|
||||||
|
r'<h2>(.*?)<', webpage, 'title')
|
||||||
|
duration = parse_duration(self._html_search_regex(
|
||||||
|
r'<span class="duration">\s*-?\s*(.*?)</span>',
|
||||||
|
webpage, 'duration', fatal=False))
|
||||||
family_friendly = self._html_search_meta(
|
family_friendly = self._html_search_meta(
|
||||||
'isFamilyFriendly', webpage, default='false')
|
'isFamilyFriendly', webpage, default='false')
|
||||||
config_url = self._search_regex(
|
|
||||||
r"'config'\s*:\s*'([^']+)'", webpage, 'config URL')
|
|
||||||
|
|
||||||
config = self._download_xml(
|
flashvars = compat_parse_qs(self._html_search_regex(
|
||||||
config_url, video_id, 'Downloading player config XML')
|
r'<embed.+?id="flash-player-embed".+?flashvars="([^"]+)"',
|
||||||
|
webpage, 'flashvars'))
|
||||||
if config is None:
|
thumbnail = flashvars.get('url_bigthumb', [None])[0]
|
||||||
raise ExtractorError('Missing config XML')
|
video_url = flashvars['flv_url'][0]
|
||||||
if config.tag != 'config':
|
|
||||||
raise ExtractorError('Missing config attribute')
|
|
||||||
fns = config.findall('file')
|
|
||||||
if len(fns) < 1:
|
|
||||||
raise ExtractorError('Missing media URI')
|
|
||||||
video_url = fns[0].text
|
|
||||||
|
|
||||||
url_comp = compat_urlparse.urlparse(url)
|
|
||||||
ref = "%s://%s%s" % (url_comp[0], url_comp[1], url_comp[2])
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'title': title,
|
'title': title,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'http_referer': ref,
|
'duration': duration,
|
||||||
'age_limit': 0 if family_friendly == 'true' else 18,
|
'age_limit': 0 if family_friendly == 'true' else 18,
|
||||||
}
|
}
|
||||||
|
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import js_to_json
|
||||||
|
|
||||||
|
|
||||||
class HelsinkiIE(InfoExtractor):
|
class HelsinkiIE(InfoExtractor):
|
||||||
@@ -24,39 +23,21 @@ class HelsinkiIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
formats = []
|
|
||||||
|
|
||||||
mobj = re.search(r'file=((\w+):[^&]+)', webpage)
|
|
||||||
if mobj:
|
|
||||||
formats.append({
|
|
||||||
'ext': mobj.group(2),
|
|
||||||
'play_path': mobj.group(1),
|
|
||||||
'url': 'rtmp://flashvideo.it.helsinki.fi/vod/',
|
|
||||||
'player_url': 'http://video.helsinki.fi/player.swf',
|
|
||||||
'format_note': 'sd',
|
|
||||||
'quality': 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
mobj = re.search(r'hd\.file=((\w+):[^&]+)', webpage)
|
|
||||||
if mobj:
|
|
||||||
formats.append({
|
|
||||||
'ext': mobj.group(2),
|
|
||||||
'play_path': mobj.group(1),
|
|
||||||
'url': 'rtmp://flashvideo.it.helsinki.fi/vod/',
|
|
||||||
'player_url': 'http://video.helsinki.fi/player.swf',
|
|
||||||
'format_note': 'hd',
|
|
||||||
'quality': 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
params = self._parse_json(self._html_search_regex(
|
||||||
|
r'(?s)jwplayer\("player"\).setup\((\{.*?\})\);',
|
||||||
|
webpage, 'player code'), video_id, transform_source=js_to_json)
|
||||||
|
formats = [{
|
||||||
|
'url': s['file'],
|
||||||
|
'ext': 'mp4',
|
||||||
|
} for s in params['sources']]
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': self._og_search_title(webpage).replace('Video: ', ''),
|
'title': self._og_search_title(webpage).replace('Video: ', ''),
|
||||||
'description': self._og_search_description(webpage),
|
'description': self._og_search_description(webpage),
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
import random
|
|
||||||
import string
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import find_xpath_attr
|
from ..utils import (
|
||||||
|
find_xpath_attr,
|
||||||
|
int_or_none,
|
||||||
|
js_to_json,
|
||||||
|
unescapeHTML,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class HowStuffWorksIE(InfoExtractor):
|
class HowStuffWorksIE(InfoExtractor):
|
||||||
@@ -16,98 +16,74 @@ class HowStuffWorksIE(InfoExtractor):
|
|||||||
'url': 'http://adventure.howstuffworks.com/5266-cool-jobs-iditarod-musher-video.htm',
|
'url': 'http://adventure.howstuffworks.com/5266-cool-jobs-iditarod-musher-video.htm',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '450221',
|
'id': '450221',
|
||||||
'display_id': 'cool-jobs-iditarod-musher',
|
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Cool Jobs - Iditarod Musher',
|
'title': 'Cool Jobs - Iditarod Musher',
|
||||||
'description': 'md5:82bb58438a88027b8186a1fccb365f90',
|
'description': 'Cold sleds, freezing temps and warm dog breath... an Iditarod musher\'s dream. Kasey-Dee Gardner jumps on a sled to find out what the big deal is.',
|
||||||
|
'display_id': 'cool-jobs-iditarod-musher',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'duration': 161,
|
||||||
},
|
},
|
||||||
'params': {
|
|
||||||
# md5 is not consistent
|
|
||||||
'skip_download': True
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://adventure.howstuffworks.com/7199-survival-zone-food-and-water-in-the-savanna-video.htm',
|
'url': 'http://adventure.howstuffworks.com/7199-survival-zone-food-and-water-in-the-savanna-video.htm',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '453464',
|
'id': '453464',
|
||||||
'display_id': 'survival-zone-food-and-water-in-the-savanna',
|
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Survival Zone: Food and Water In the Savanna',
|
'title': 'Survival Zone: Food and Water In the Savanna',
|
||||||
'description': 'md5:7e1c89f6411434970c15fa094170c371',
|
'description': 'Learn how to find both food and water while trekking in the African savannah. In this video from the Discovery Channel.',
|
||||||
|
'display_id': 'survival-zone-food-and-water-in-the-savanna',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
},
|
},
|
||||||
'params': {
|
|
||||||
# md5 is not consistent
|
|
||||||
'skip_download': True
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://entertainment.howstuffworks.com/arts/2706-sword-swallowing-1-by-dan-meyer-video.htm',
|
'url': 'http://entertainment.howstuffworks.com/arts/2706-sword-swallowing-1-by-dan-meyer-video.htm',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '440011',
|
'id': '440011',
|
||||||
'display_id': 'sword-swallowing-1-by-dan-meyer',
|
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Sword Swallowing #1 by Dan Meyer',
|
'title': 'Sword Swallowing #1 by Dan Meyer',
|
||||||
'description': 'md5:b2409e88172913e2e7d3d1159b0ef735',
|
'description': 'Video footage (1 of 3) used by permission of the owner Dan Meyer through Sword Swallowers Association International <www.swordswallow.org>',
|
||||||
|
'display_id': 'sword-swallowing-1-by-dan-meyer',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
},
|
},
|
||||||
'params': {
|
|
||||||
# md5 is not consistent
|
|
||||||
'skip_download': True
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
display_id = self._match_id(url)
|
||||||
display_id = mobj.group('id')
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
clip_js = self._search_regex(
|
||||||
|
r'(?s)var clip = ({.*?});', webpage, 'clip info')
|
||||||
|
clip_info = self._parse_json(
|
||||||
|
clip_js, display_id, transform_source=js_to_json)
|
||||||
|
|
||||||
content_id = self._search_regex(r'var siteSectionId="(\d+)";', webpage, 'content id')
|
video_id = clip_info['content_id']
|
||||||
|
|
||||||
mp4 = self._search_regex(
|
|
||||||
r'''(?xs)var\s+clip\s*=\s*{\s*
|
|
||||||
.+?\s*
|
|
||||||
content_id\s*:\s*%s\s*,\s*
|
|
||||||
.+?\s*
|
|
||||||
mp4\s*:\s*\[(.*?),?\]\s*
|
|
||||||
};\s*
|
|
||||||
videoData\.push\(clip\);''' % content_id,
|
|
||||||
webpage, 'mp4', fatal=False, default=None)
|
|
||||||
|
|
||||||
smil = self._download_xml(
|
|
||||||
'http://services.media.howstuffworks.com/videos/%s/smil-service.smil' % content_id,
|
|
||||||
content_id, 'Downloading video SMIL')
|
|
||||||
|
|
||||||
http_base = find_xpath_attr(
|
|
||||||
smil,
|
|
||||||
'./{0}head/{0}meta'.format('{http://www.w3.org/2001/SMIL20/Language}'),
|
|
||||||
'name',
|
|
||||||
'httpBase').get('content')
|
|
||||||
|
|
||||||
def random_string(str_len=0):
|
|
||||||
return ''.join([random.choice(string.ascii_uppercase) for _ in range(str_len)])
|
|
||||||
|
|
||||||
URL_SUFFIX = '?v=2.11.3&fp=LNX 11,2,202,356&r=%s&g=%s' % (random_string(5), random_string(12))
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
m3u8_url = clip_info.get('m3u8')
|
||||||
|
if m3u8_url:
|
||||||
|
formats += self._extract_m3u8_formats(m3u8_url, video_id, 'mp4')
|
||||||
|
for video in clip_info.get('mp4', []):
|
||||||
|
formats.append({
|
||||||
|
'url': video['src'],
|
||||||
|
'format_id': video['bitrate'],
|
||||||
|
'vbr': int(video['bitrate'].rstrip('k')),
|
||||||
|
})
|
||||||
|
|
||||||
|
if not formats:
|
||||||
|
smil = self._download_xml(
|
||||||
|
'http://services.media.howstuffworks.com/videos/%s/smil-service.smil' % video_id,
|
||||||
|
video_id, 'Downloading video SMIL')
|
||||||
|
|
||||||
|
http_base = find_xpath_attr(
|
||||||
|
smil,
|
||||||
|
'./{0}head/{0}meta'.format('{http://www.w3.org/2001/SMIL20/Language}'),
|
||||||
|
'name',
|
||||||
|
'httpBase').get('content')
|
||||||
|
|
||||||
|
URL_SUFFIX = '?v=2.11.3&fp=LNX 11,2,202,356&r=A&g=A'
|
||||||
|
|
||||||
if mp4:
|
|
||||||
for video in json.loads('[%s]' % mp4):
|
|
||||||
bitrate = video['bitrate']
|
|
||||||
fmt = {
|
|
||||||
'url': video['src'].replace('http://pmd.video.howstuffworks.com', http_base) + URL_SUFFIX,
|
|
||||||
'format_id': bitrate,
|
|
||||||
}
|
|
||||||
m = re.search(r'(?P<vbr>\d+)[Kk]', bitrate)
|
|
||||||
if m:
|
|
||||||
fmt['vbr'] = int(m.group('vbr'))
|
|
||||||
formats.append(fmt)
|
|
||||||
else:
|
|
||||||
for video in smil.findall(
|
for video in smil.findall(
|
||||||
'.//{0}body/{0}switch/{0}video'.format('{http://www.w3.org/2001/SMIL20/Language}')):
|
'./{0}body/{0}switch/{0}video'.format('{http://www.w3.org/2001/SMIL20/Language}')):
|
||||||
vbr = int(video.attrib['system-bitrate']) / 1000
|
vbr = int_or_none(video.attrib['system-bitrate'], scale=1000)
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': '%s/%s%s' % (http_base, video.attrib['src'], URL_SUFFIX),
|
'url': '%s/%s%s' % (http_base, video.attrib['src'], URL_SUFFIX),
|
||||||
'format_id': '%dk' % vbr,
|
'format_id': '%dk' % vbr,
|
||||||
@@ -116,19 +92,12 @@ class HowStuffWorksIE(InfoExtractor):
|
|||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
title = self._og_search_title(webpage)
|
|
||||||
TITLE_SUFFIX = ' : HowStuffWorks'
|
|
||||||
if title.endswith(TITLE_SUFFIX):
|
|
||||||
title = title[:-len(TITLE_SUFFIX)]
|
|
||||||
|
|
||||||
description = self._og_search_description(webpage)
|
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': content_id,
|
'id': '%s' % video_id,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': title,
|
'title': unescapeHTML(clip_info['clip_title']),
|
||||||
'description': description,
|
'description': unescapeHTML(clip_info.get('caption')),
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': clip_info.get('video_still_url'),
|
||||||
|
'duration': clip_info.get('duration'),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@@ -70,7 +70,7 @@ class MixcloudIE(InfoExtractor):
|
|||||||
raise ExtractorError('Unable to extract track url')
|
raise ExtractorError('Unable to extract track url')
|
||||||
|
|
||||||
PREFIX = (
|
PREFIX = (
|
||||||
r'<div class="cloudcast-play-button-container[^"]*?"'
|
r'<span class="play-button[^"]*?"'
|
||||||
r'(?:\s+[a-zA-Z0-9-]+(?:="[^"]+")?)*?\s+')
|
r'(?:\s+[a-zA-Z0-9-]+(?:="[^"]+")?)*?\s+')
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
PREFIX + r'm-title="([^"]+)"', webpage, 'title')
|
PREFIX + r'm-title="([^"]+)"', webpage, 'title')
|
||||||
|
@@ -130,7 +130,7 @@ class NTVIE(InfoExtractor):
|
|||||||
'rtmp_conn': 'B:1',
|
'rtmp_conn': 'B:1',
|
||||||
'player_url': 'http://www.ntv.ru/swf/vps1.swf?update=20131128',
|
'player_url': 'http://www.ntv.ru/swf/vps1.swf?update=20131128',
|
||||||
'page_url': 'http://www.ntv.ru',
|
'page_url': 'http://www.ntv.ru',
|
||||||
'flash_ver': 'LNX 11,2,202,341',
|
'flash_version': 'LNX 11,2,202,341',
|
||||||
'rtmp_live': True,
|
'rtmp_live': True,
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'filesize': int(size.text),
|
'filesize': int(size.text),
|
||||||
|
@@ -8,7 +8,6 @@ from ..utils import (
|
|||||||
int_or_none,
|
int_or_none,
|
||||||
js_to_json,
|
js_to_json,
|
||||||
qualities,
|
qualities,
|
||||||
determine_ext,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -45,13 +44,18 @@ class PornHdIE(InfoExtractor):
|
|||||||
thumbnail = self._search_regex(
|
thumbnail = self._search_regex(
|
||||||
r"'poster'\s*:\s*'([^']+)'", webpage, 'thumbnail', fatal=False)
|
r"'poster'\s*:\s*'([^']+)'", webpage, 'thumbnail', fatal=False)
|
||||||
|
|
||||||
quality = qualities(['SD', 'HD'])
|
quality = qualities(['sd', 'hd'])
|
||||||
formats = [{
|
sources = json.loads(js_to_json(self._search_regex(
|
||||||
'url': source['file'],
|
r"(?s)'sources'\s*:\s*(\{.+?\})\s*\}\);", webpage, 'sources')))
|
||||||
'format_id': '%s-%s' % (source['label'], determine_ext(source['file'])),
|
formats = []
|
||||||
'quality': quality(source['label']),
|
for container, s in sources.items():
|
||||||
} for source in json.loads(js_to_json(self._search_regex(
|
for qname, video_url in s.items():
|
||||||
r"(?s)'sources'\s*:\s*(\[.+?\])", webpage, 'sources')))]
|
formats.append({
|
||||||
|
'url': video_url,
|
||||||
|
'container': container,
|
||||||
|
'format_id': '%s-%s' % (container, qname),
|
||||||
|
'quality': quality(qname),
|
||||||
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -5,61 +5,27 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
unified_strdate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CinemassacreIE(InfoExtractor):
|
class ScreenwaveMediaIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://(?:www\.)?cinemassacre\.com/(?P<date_Y>[0-9]{4})/(?P<date_m>[0-9]{2})/(?P<date_d>[0-9]{2})/(?P<display_id>[^?#/]+)'
|
_VALID_URL = r'http://player\.screenwavemedia\.com/play/[a-zA-Z]+\.php\?[^"]*\bid=(?P<id>.+)'
|
||||||
_TESTS = [
|
|
||||||
{
|
_TESTS = [{
|
||||||
'url': 'http://cinemassacre.com/2012/11/10/avgn-the-movie-trailer/',
|
'url': 'http://player.screenwavemedia.com/play/play.php?playerdiv=videoarea&companiondiv=squareAd&id=Cinemassacre-19911',
|
||||||
'md5': 'fde81fbafaee331785f58cd6c0d46190',
|
'only_matching': True,
|
||||||
'info_dict': {
|
}]
|
||||||
'id': '19911',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'upload_date': '20121110',
|
|
||||||
'title': '“Angry Video Game Nerd: The Movie” – Trailer',
|
|
||||||
'description': 'md5:fb87405fcb42a331742a0dce2708560b',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'url': 'http://cinemassacre.com/2013/10/02/the-mummys-hand-1940',
|
|
||||||
'md5': 'd72f10cd39eac4215048f62ab477a511',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '521be8ef82b16',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'upload_date': '20131002',
|
|
||||||
'title': 'The Mummy’s Hand (1940)',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
display_id = mobj.group('display_id')
|
playerdata = self._download_webpage(url, video_id, 'Downloading player webpage')
|
||||||
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
|
||||||
video_date = mobj.group('date_Y') + mobj.group('date_m') + mobj.group('date_d')
|
|
||||||
mobj = re.search(r'src="(?P<embed_url>http://player\.screenwavemedia\.com/play/[a-zA-Z]+\.php\?[^"]*\bid=(?P<full_video_id>(?:Cinemassacre-)?(?P<video_id>.+?)))"', webpage)
|
|
||||||
if not mobj:
|
|
||||||
raise ExtractorError('Can\'t extract embed url and video id')
|
|
||||||
playerdata_url = mobj.group('embed_url')
|
|
||||||
video_id = mobj.group('video_id')
|
|
||||||
full_video_id = mobj.group('full_video_id')
|
|
||||||
|
|
||||||
video_title = self._html_search_regex(
|
|
||||||
r'<title>(?P<title>.+?)\|', webpage, 'title')
|
|
||||||
video_description = self._html_search_regex(
|
|
||||||
r'<div class="entry-content">(?P<description>.+?)</div>',
|
|
||||||
webpage, 'description', flags=re.DOTALL, fatal=False)
|
|
||||||
video_thumbnail = self._og_search_thumbnail(webpage)
|
|
||||||
|
|
||||||
playerdata = self._download_webpage(playerdata_url, video_id, 'Downloading player webpage')
|
|
||||||
|
|
||||||
|
vidtitle = self._search_regex(
|
||||||
|
r'\'vidtitle\'\s*:\s*"([^"]+)"', playerdata, 'vidtitle').replace('\\/', '/')
|
||||||
vidurl = self._search_regex(
|
vidurl = self._search_regex(
|
||||||
r'\'vidurl\'\s*:\s*"([^\']+)"', playerdata, 'vidurl').replace('\\/', '/')
|
r'\'vidurl\'\s*:\s*"([^"]+)"', playerdata, 'vidurl').replace('\\/', '/')
|
||||||
|
|
||||||
videolist_url = None
|
videolist_url = None
|
||||||
|
|
||||||
@@ -67,7 +33,7 @@ class CinemassacreIE(InfoExtractor):
|
|||||||
if mobj:
|
if mobj:
|
||||||
videoserver = mobj.group('videoserver')
|
videoserver = mobj.group('videoserver')
|
||||||
mobj = re.search(r'\'vidid\'\s*:\s*"(?P<vidid>[^\']+)"', playerdata)
|
mobj = re.search(r'\'vidid\'\s*:\s*"(?P<vidid>[^\']+)"', playerdata)
|
||||||
vidid = mobj.group('vidid') if mobj else full_video_id
|
vidid = mobj.group('vidid') if mobj else video_id
|
||||||
videolist_url = 'http://%s/vod/smil:%s.smil/jwplayer.smil' % (videoserver, vidid)
|
videolist_url = 'http://%s/vod/smil:%s.smil/jwplayer.smil' % (videoserver, vidid)
|
||||||
else:
|
else:
|
||||||
mobj = re.search(r"file\s*:\s*'(?P<smil>http.+?/jwplayer\.smil)'", playerdata)
|
mobj = re.search(r"file\s*:\s*'(?P<smil>http.+?/jwplayer\.smil)'", playerdata)
|
||||||
@@ -85,34 +51,128 @@ class CinemassacreIE(InfoExtractor):
|
|||||||
file_ = src.partition(':')[-1]
|
file_ = src.partition(':')[-1]
|
||||||
width = int_or_none(video.get('width'))
|
width = int_or_none(video.get('width'))
|
||||||
height = int_or_none(video.get('height'))
|
height = int_or_none(video.get('height'))
|
||||||
bitrate = int_or_none(video.get('system-bitrate'))
|
bitrate = int_or_none(video.get('system-bitrate'), scale=1000)
|
||||||
format = {
|
format = {
|
||||||
'url': baseurl + file_,
|
'url': baseurl + file_,
|
||||||
'format_id': src.rpartition('.')[0].rpartition('_')[-1],
|
'format_id': src.rpartition('.')[0].rpartition('_')[-1],
|
||||||
}
|
}
|
||||||
if width or height:
|
if width or height:
|
||||||
format.update({
|
format.update({
|
||||||
'tbr': bitrate // 1000 if bitrate else None,
|
'tbr': bitrate,
|
||||||
'width': width,
|
'width': width,
|
||||||
'height': height,
|
'height': height,
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
format.update({
|
format.update({
|
||||||
'abr': bitrate // 1000 if bitrate else None,
|
'abr': bitrate,
|
||||||
'vcodec': 'none',
|
'vcodec': 'none',
|
||||||
})
|
})
|
||||||
formats.append(format)
|
formats.append(format)
|
||||||
self._sort_formats(formats)
|
|
||||||
else:
|
else:
|
||||||
formats = [{
|
formats = [{
|
||||||
'url': vidurl,
|
'url': vidurl,
|
||||||
}]
|
}]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': video_title,
|
'title': vidtitle,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CinemassacreIE(InfoExtractor):
|
||||||
|
_VALID_URL = 'https?://(?:www\.)?cinemassacre\.com/(?P<date_y>[0-9]{4})/(?P<date_m>[0-9]{2})/(?P<date_d>[0-9]{2})/(?P<display_id>[^?#/]+)'
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://cinemassacre.com/2012/11/10/avgn-the-movie-trailer/',
|
||||||
|
'md5': 'fde81fbafaee331785f58cd6c0d46190',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'Cinemassacre-19911',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'upload_date': '20121110',
|
||||||
|
'title': '“Angry Video Game Nerd: The Movie” – Trailer',
|
||||||
|
'description': 'md5:fb87405fcb42a331742a0dce2708560b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://cinemassacre.com/2013/10/02/the-mummys-hand-1940',
|
||||||
|
'md5': 'd72f10cd39eac4215048f62ab477a511',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'Cinemassacre-521be8ef82b16',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'upload_date': '20131002',
|
||||||
|
'title': 'The Mummy’s Hand (1940)',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
display_id = mobj.group('display_id')
|
||||||
|
video_date = mobj.group('date_y') + mobj.group('date_m') + mobj.group('date_d')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
playerdata_url = self._search_regex(
|
||||||
|
r'src="(http://player\.screenwavemedia\.com/play/[a-zA-Z]+\.php\?[^"]*\bid=.+?)"',
|
||||||
|
webpage, 'player data URL')
|
||||||
|
video_title = self._html_search_regex(
|
||||||
|
r'<title>(?P<title>.+?)\|', webpage, 'title')
|
||||||
|
video_description = self._html_search_regex(
|
||||||
|
r'<div class="entry-content">(?P<description>.+?)</div>',
|
||||||
|
webpage, 'description', flags=re.DOTALL, fatal=False)
|
||||||
|
video_thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': video_title,
|
||||||
'description': video_description,
|
'description': video_description,
|
||||||
'upload_date': video_date,
|
'upload_date': video_date,
|
||||||
'thumbnail': video_thumbnail,
|
'thumbnail': video_thumbnail,
|
||||||
|
'url': playerdata_url,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TeamFourIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?teamfourstar\.com/video/(?P<id>[a-z0-9\-]+)/?'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://teamfourstar.com/video/a-moment-with-tfs-episode-4/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'TeamFourStar-5292a02f20bfa',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'upload_date': '20130401',
|
||||||
|
'description': 'Check out this and more on our website: http://teamfourstar.com\nTFS Store: http://sharkrobot.com/team-four-star\nFollow on Twitter: http://twitter.com/teamfourstar\nLike on FB: http://facebook.com/teamfourstar',
|
||||||
|
'title': 'A Moment With TFS Episode 4',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
playerdata_url = self._search_regex(
|
||||||
|
r'src="(http://player\.screenwavemedia\.com/play/[a-zA-Z]+\.php\?[^"]*\bid=.+?)"',
|
||||||
|
webpage, 'player data URL')
|
||||||
|
|
||||||
|
video_title = self._html_search_regex(
|
||||||
|
r'<div class="heroheadingtitle">(?P<title>.+?)</div>',
|
||||||
|
webpage, 'title')
|
||||||
|
video_date = unified_strdate(self._html_search_regex(
|
||||||
|
r'<div class="heroheadingdate">(?P<date>.+?)</div>',
|
||||||
|
webpage, 'date', fatal=False))
|
||||||
|
video_description = self._html_search_regex(
|
||||||
|
r'(?s)<div class="postcontent">(?P<description>.+?)</div>',
|
||||||
|
webpage, 'description', fatal=False)
|
||||||
|
video_thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': video_title,
|
||||||
|
'description': video_description,
|
||||||
|
'upload_date': video_date,
|
||||||
|
'thumbnail': video_thumbnail,
|
||||||
|
'url': playerdata_url,
|
||||||
}
|
}
|
@@ -274,15 +274,18 @@ class SmotriBroadcastIE(InfoExtractor):
|
|||||||
broadcast_page = self._download_webpage(broadcast_url, broadcast_id, 'Downloading broadcast page')
|
broadcast_page = self._download_webpage(broadcast_url, broadcast_id, 'Downloading broadcast page')
|
||||||
|
|
||||||
if re.search('>Режиссер с логином <br/>"%s"<br/> <span>не существует<' % broadcast_id, broadcast_page) is not None:
|
if re.search('>Режиссер с логином <br/>"%s"<br/> <span>не существует<' % broadcast_id, broadcast_page) is not None:
|
||||||
raise ExtractorError('Broadcast %s does not exist' % broadcast_id, expected=True)
|
raise ExtractorError(
|
||||||
|
'Broadcast %s does not exist' % broadcast_id, expected=True)
|
||||||
|
|
||||||
# Adult content
|
# Adult content
|
||||||
if re.search('EroConfirmText">', broadcast_page) is not None:
|
if re.search('EroConfirmText">', broadcast_page) is not None:
|
||||||
|
|
||||||
(username, password) = self._get_login_info()
|
(username, password) = self._get_login_info()
|
||||||
if username is None:
|
if username is None:
|
||||||
raise ExtractorError('Erotic broadcasts allowed only for registered users, '
|
raise ExtractorError(
|
||||||
'use --username and --password options to provide account credentials.', expected=True)
|
'Erotic broadcasts allowed only for registered users, '
|
||||||
|
'use --username and --password options to provide account credentials.',
|
||||||
|
expected=True)
|
||||||
|
|
||||||
login_form = {
|
login_form = {
|
||||||
'login-hint53': '1',
|
'login-hint53': '1',
|
||||||
@@ -291,9 +294,11 @@ class SmotriBroadcastIE(InfoExtractor):
|
|||||||
'password': password,
|
'password': password,
|
||||||
}
|
}
|
||||||
|
|
||||||
request = compat_urllib_request.Request(broadcast_url + '/?no_redirect=1', compat_urllib_parse.urlencode(login_form))
|
request = compat_urllib_request.Request(
|
||||||
|
broadcast_url + '/?no_redirect=1', compat_urllib_parse.urlencode(login_form))
|
||||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
broadcast_page = self._download_webpage(request, broadcast_id, 'Logging in and confirming age')
|
broadcast_page = self._download_webpage(
|
||||||
|
request, broadcast_id, 'Logging in and confirming age')
|
||||||
|
|
||||||
if re.search('>Неверный логин или пароль<', broadcast_page) is not None:
|
if re.search('>Неверный логин или пароль<', broadcast_page) is not None:
|
||||||
raise ExtractorError('Unable to log in: bad username or password', expected=True)
|
raise ExtractorError('Unable to log in: bad username or password', expected=True)
|
||||||
@@ -303,7 +308,7 @@ class SmotriBroadcastIE(InfoExtractor):
|
|||||||
adult_content = False
|
adult_content = False
|
||||||
|
|
||||||
ticket = self._html_search_regex(
|
ticket = self._html_search_regex(
|
||||||
'window\.broadcast_control\.addFlashVar\\(\'file\', \'([^\']+)\'\\);',
|
r"window\.broadcast_control\.addFlashVar\('file'\s*,\s*'([^']+)'\)",
|
||||||
broadcast_page, 'broadcast ticket')
|
broadcast_page, 'broadcast ticket')
|
||||||
|
|
||||||
url = 'http://smotri.com/broadcast/view/url/?ticket=%s' % ticket
|
url = 'http://smotri.com/broadcast/view/url/?ticket=%s' % ticket
|
||||||
@@ -312,26 +317,31 @@ class SmotriBroadcastIE(InfoExtractor):
|
|||||||
if broadcast_password:
|
if broadcast_password:
|
||||||
url += '&pass=%s' % hashlib.md5(broadcast_password.encode('utf-8')).hexdigest()
|
url += '&pass=%s' % hashlib.md5(broadcast_password.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
broadcast_json_page = self._download_webpage(url, broadcast_id, 'Downloading broadcast JSON')
|
broadcast_json_page = self._download_webpage(
|
||||||
|
url, broadcast_id, 'Downloading broadcast JSON')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
broadcast_json = json.loads(broadcast_json_page)
|
broadcast_json = json.loads(broadcast_json_page)
|
||||||
|
|
||||||
protected_broadcast = broadcast_json['_pass_protected'] == 1
|
protected_broadcast = broadcast_json['_pass_protected'] == 1
|
||||||
if protected_broadcast and not broadcast_password:
|
if protected_broadcast and not broadcast_password:
|
||||||
raise ExtractorError('This broadcast is protected by a password, use the --video-password option', expected=True)
|
raise ExtractorError(
|
||||||
|
'This broadcast is protected by a password, use the --video-password option',
|
||||||
|
expected=True)
|
||||||
|
|
||||||
broadcast_offline = broadcast_json['is_play'] == 0
|
broadcast_offline = broadcast_json['is_play'] == 0
|
||||||
if broadcast_offline:
|
if broadcast_offline:
|
||||||
raise ExtractorError('Broadcast %s is offline' % broadcast_id, expected=True)
|
raise ExtractorError('Broadcast %s is offline' % broadcast_id, expected=True)
|
||||||
|
|
||||||
rtmp_url = broadcast_json['_server']
|
rtmp_url = broadcast_json['_server']
|
||||||
if not rtmp_url.startswith('rtmp://'):
|
mobj = re.search(r'^rtmp://[^/]+/(?P<app>.+)/?$', rtmp_url)
|
||||||
|
if not mobj:
|
||||||
raise ExtractorError('Unexpected broadcast rtmp URL')
|
raise ExtractorError('Unexpected broadcast rtmp URL')
|
||||||
|
|
||||||
broadcast_playpath = broadcast_json['_streamName']
|
broadcast_playpath = broadcast_json['_streamName']
|
||||||
|
broadcast_app = '%s/%s' % (mobj.group('app'), broadcast_json['_vidURL'])
|
||||||
broadcast_thumbnail = broadcast_json['_imgURL']
|
broadcast_thumbnail = broadcast_json['_imgURL']
|
||||||
broadcast_title = broadcast_json['title']
|
broadcast_title = self._live_title(broadcast_json['title'])
|
||||||
broadcast_description = broadcast_json['description']
|
broadcast_description = broadcast_json['description']
|
||||||
broadcaster_nick = broadcast_json['nick']
|
broadcaster_nick = broadcast_json['nick']
|
||||||
broadcaster_login = broadcast_json['login']
|
broadcaster_login = broadcast_json['login']
|
||||||
@@ -352,6 +362,9 @@ class SmotriBroadcastIE(InfoExtractor):
|
|||||||
'age_limit': 18 if adult_content else 0,
|
'age_limit': 18 if adult_content else 0,
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'play_path': broadcast_playpath,
|
'play_path': broadcast_playpath,
|
||||||
|
'player_url': 'http://pics.smotri.com/broadcast_play.swf',
|
||||||
|
'app': broadcast_app,
|
||||||
'rtmp_live': True,
|
'rtmp_live': True,
|
||||||
'rtmp_conn': rtmp_conn
|
'rtmp_conn': rtmp_conn,
|
||||||
|
'is_live': True,
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,6 @@ import re
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_str
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
qualities,
|
qualities,
|
||||||
)
|
)
|
||||||
@@ -182,8 +181,8 @@ class TVPlayIE(InfoExtractor):
|
|||||||
'http://playapi.mtgx.tv/v1/videos/%s' % video_id, video_id, 'Downloading video JSON')
|
'http://playapi.mtgx.tv/v1/videos/%s' % video_id, video_id, 'Downloading video JSON')
|
||||||
|
|
||||||
if video['is_geo_blocked']:
|
if video['is_geo_blocked']:
|
||||||
raise ExtractorError(
|
self.report_warning(
|
||||||
'This content is not available in your country due to copyright reasons', expected=True)
|
'This content might not be available in your country due to copyright reasons')
|
||||||
|
|
||||||
streams = self._download_json(
|
streams = self._download_json(
|
||||||
'http://playapi.mtgx.tv/v1/videos/stream/%s' % video_id, video_id, 'Downloading streams JSON')
|
'http://playapi.mtgx.tv/v1/videos/stream/%s' % video_id, video_id, 'Downloading streams JSON')
|
||||||
|
@@ -17,6 +17,7 @@ class VineIE(InfoExtractor):
|
|||||||
'id': 'b9KOOWX7HUx',
|
'id': 'b9KOOWX7HUx',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Chicken.',
|
'title': 'Chicken.',
|
||||||
|
'alt_title': 'Vine by Jack Dorsey',
|
||||||
'description': 'Chicken.',
|
'description': 'Chicken.',
|
||||||
'upload_date': '20130519',
|
'upload_date': '20130519',
|
||||||
'uploader': 'Jack Dorsey',
|
'uploader': 'Jack Dorsey',
|
||||||
@@ -25,30 +26,26 @@ class VineIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage('https://vine.co/v/' + video_id, video_id)
|
webpage = self._download_webpage('https://vine.co/v/' + video_id, video_id)
|
||||||
|
|
||||||
data = json.loads(self._html_search_regex(
|
data = json.loads(self._html_search_regex(
|
||||||
r'window\.POST_DATA = { %s: ({.+?}) }' % video_id, webpage, 'vine data'))
|
r'window\.POST_DATA = { %s: ({.+?}) }' % video_id, webpage, 'vine data'))
|
||||||
|
|
||||||
formats = [
|
formats = [{
|
||||||
{
|
'url': data['videoLowURL'],
|
||||||
'url': data['videoLowURL'],
|
'ext': 'mp4',
|
||||||
'ext': 'mp4',
|
'format_id': 'low',
|
||||||
'format_id': 'low',
|
}, {
|
||||||
},
|
'url': data['videoUrl'],
|
||||||
{
|
'ext': 'mp4',
|
||||||
'url': data['videoUrl'],
|
'format_id': 'standard',
|
||||||
'ext': 'mp4',
|
}]
|
||||||
'format_id': 'standard',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': self._og_search_title(webpage),
|
'title': self._og_search_title(webpage),
|
||||||
|
'alt_title': self._og_search_description(webpage),
|
||||||
'description': data['description'],
|
'description': data['description'],
|
||||||
'thumbnail': data['thumbnailUrl'],
|
'thumbnail': data['thumbnailUrl'],
|
||||||
'upload_date': unified_strdate(data['created']),
|
'upload_date': unified_strdate(data['created']),
|
||||||
|
@@ -14,23 +14,24 @@ from .common import InfoExtractor, SearchInfoExtractor
|
|||||||
from .subtitles import SubtitlesInfoExtractor
|
from .subtitles import SubtitlesInfoExtractor
|
||||||
from ..jsinterp import JSInterpreter
|
from ..jsinterp import JSInterpreter
|
||||||
from ..swfinterp import SWFInterpreter
|
from ..swfinterp import SWFInterpreter
|
||||||
from ..utils import (
|
from ..compat import (
|
||||||
compat_chr,
|
compat_chr,
|
||||||
compat_parse_qs,
|
compat_parse_qs,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
compat_str,
|
compat_str,
|
||||||
|
)
|
||||||
|
from ..utils import (
|
||||||
clean_html,
|
clean_html,
|
||||||
get_element_by_id,
|
|
||||||
get_element_by_attribute,
|
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
get_element_by_attribute,
|
||||||
|
get_element_by_id,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
OnDemandPagedList,
|
OnDemandPagedList,
|
||||||
|
orderedSet,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
orderedSet,
|
|
||||||
uppercase_escape,
|
uppercase_escape,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -432,7 +433,23 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
'expected_warnings': [
|
'expected_warnings': [
|
||||||
'DASH manifest missing',
|
'DASH manifest missing',
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
# Olympics (https://github.com/rg3/youtube-dl/issues/4431)
|
||||||
|
{
|
||||||
|
'url': 'lqQg6PlCWgI',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'lqQg6PlCWgI',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'upload_date': '20120731',
|
||||||
|
'uploader_id': 'olympic',
|
||||||
|
'description': 'HO09 - Women - GER-AUS - Hockey - 31 July 2012 - London 2012 Olympic Games',
|
||||||
|
'uploader': 'Olympics',
|
||||||
|
'title': 'Hockey - Women - GER-AUS - London 2012 Olympic Games',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': 'requires avconv',
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -856,7 +873,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
|
|
||||||
m_cat_container = self._search_regex(
|
m_cat_container = self._search_regex(
|
||||||
r'(?s)<h4[^>]*>\s*Category\s*</h4>\s*<ul[^>]*>(.*?)</ul>',
|
r'(?s)<h4[^>]*>\s*Category\s*</h4>\s*<ul[^>]*>(.*?)</ul>',
|
||||||
video_webpage, 'categories', fatal=False)
|
video_webpage, 'categories', default=None)
|
||||||
if m_cat_container:
|
if m_cat_container:
|
||||||
category = self._html_search_regex(
|
category = self._html_search_regex(
|
||||||
r'(?s)<a[^<]+>(.*?)</a>', m_cat_container, 'category',
|
r'(?s)<a[^<]+>(.*?)</a>', m_cat_container, 'category',
|
||||||
@@ -934,7 +951,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
'url': video_info['conn'][0],
|
'url': video_info['conn'][0],
|
||||||
'player_url': player_url,
|
'player_url': player_url,
|
||||||
}]
|
}]
|
||||||
elif len(video_info.get('url_encoded_fmt_stream_map', [])) >= 1 or len(video_info.get('adaptive_fmts', [])) >= 1:
|
elif len(video_info.get('url_encoded_fmt_stream_map', [''])[0]) >= 1 or len(video_info.get('adaptive_fmts', [''])[0]) >= 1:
|
||||||
encoded_url_map = video_info.get('url_encoded_fmt_stream_map', [''])[0] + ',' + video_info.get('adaptive_fmts', [''])[0]
|
encoded_url_map = video_info.get('url_encoded_fmt_stream_map', [''])[0] + ',' + video_info.get('adaptive_fmts', [''])[0]
|
||||||
if 'rtmpe%3Dyes' in encoded_url_map:
|
if 'rtmpe%3Dyes' in encoded_url_map:
|
||||||
raise ExtractorError('rtmpe downloads are not supported, see https://github.com/rg3/youtube-dl/issues/343 for more information.', expected=True)
|
raise ExtractorError('rtmpe downloads are not supported, see https://github.com/rg3/youtube-dl/issues/343 for more information.', expected=True)
|
||||||
@@ -1000,9 +1017,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
# Look for the DASH manifest
|
# Look for the DASH manifest
|
||||||
if self._downloader.params.get('youtube_include_dash_manifest', True):
|
if self._downloader.params.get('youtube_include_dash_manifest', True):
|
||||||
dash_mpd = video_info.get('dashmpd')
|
dash_mpd = video_info.get('dashmpd')
|
||||||
if not dash_mpd:
|
if dash_mpd:
|
||||||
self.report_warning('%s: DASH manifest missing' % video_id)
|
|
||||||
else:
|
|
||||||
dash_manifest_url = dash_mpd[0]
|
dash_manifest_url = dash_mpd[0]
|
||||||
try:
|
try:
|
||||||
dash_formats = self._parse_dash_manifest(
|
dash_formats = self._parse_dash_manifest(
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import functools
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
|
OnDemandPagedList,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -87,7 +89,7 @@ def extract_from_xml_url(ie, video_id, xml_url):
|
|||||||
|
|
||||||
|
|
||||||
class ZDFIE(InfoExtractor):
|
class ZDFIE(InfoExtractor):
|
||||||
_VALID_URL = r'^https?://www\.zdf\.de/ZDFmediathek(?P<hash>#)?/(.*beitrag/(?:video/)?)(?P<id>[0-9]+)(?:/[^/?]+)?(?:\?.*)?'
|
_VALID_URL = r'(?:zdf:|zdf:video:|https?://www\.zdf\.de/ZDFmediathek(?:#)?/(.*beitrag/(?:video/)?))(?P<id>[0-9]+)(?:/[^/?]+)?(?:\?.*)?'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.zdf.de/ZDFmediathek/beitrag/video/2037704/ZDFspezial---Ende-des-Machtpokers--?bc=sts;stt',
|
'url': 'http://www.zdf.de/ZDFmediathek/beitrag/video/2037704/ZDFspezial---Ende-des-Machtpokers--?bc=sts;stt',
|
||||||
@@ -106,6 +108,52 @@ class ZDFIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
xml_url = 'http://www.zdf.de/ZDFmediathek/xmlservice/web/beitragsDetails?ak=web&id=%s' % video_id
|
xml_url = 'http://www.zdf.de/ZDFmediathek/xmlservice/web/beitragsDetails?ak=web&id=%s' % video_id
|
||||||
return extract_from_xml_url(self, video_id, xml_url)
|
return extract_from_xml_url(self, video_id, xml_url)
|
||||||
|
|
||||||
|
|
||||||
|
class ZDFChannelIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'(?:zdf:topic:|https?://www\.zdf\.de/ZDFmediathek(?:#)?/.*kanaluebersicht/)(?P<id>[0-9]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.zdf.de/ZDFmediathek#/kanaluebersicht/1586442/sendung/Titanic',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1586442',
|
||||||
|
},
|
||||||
|
'playlist_count': 4,
|
||||||
|
}
|
||||||
|
_PAGE_SIZE = 50
|
||||||
|
|
||||||
|
def _fetch_page(self, channel_id, page):
|
||||||
|
offset = page * self._PAGE_SIZE
|
||||||
|
xml_url = (
|
||||||
|
'http://www.zdf.de/ZDFmediathek/xmlservice/web/aktuellste?ak=web&offset=%d&maxLength=%d&id=%s'
|
||||||
|
% (offset, self._PAGE_SIZE, channel_id))
|
||||||
|
doc = self._download_xml(
|
||||||
|
xml_url, channel_id,
|
||||||
|
note='Downloading channel info',
|
||||||
|
errnote='Failed to download channel info')
|
||||||
|
|
||||||
|
title = doc.find('.//information/title').text
|
||||||
|
description = doc.find('.//information/detail').text
|
||||||
|
for asset in doc.findall('.//teasers/teaser'):
|
||||||
|
a_type = asset.find('./type').text
|
||||||
|
a_id = asset.find('./details/assetId').text
|
||||||
|
if a_type not in ('video', 'topic'):
|
||||||
|
continue
|
||||||
|
yield {
|
||||||
|
'_type': 'url',
|
||||||
|
'playlist_title': title,
|
||||||
|
'playlist_description': description,
|
||||||
|
'url': 'zdf:%s:%s' % (a_type, a_id),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
channel_id = self._match_id(url)
|
||||||
|
entries = OnDemandPagedList(
|
||||||
|
functools.partial(self._fetch_page, channel_id), self._PAGE_SIZE)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': channel_id,
|
||||||
|
'entries': entries,
|
||||||
|
}
|
||||||
|
@@ -166,7 +166,7 @@ def xpath_text(node, xpath, name=None, fatal=False):
|
|||||||
xpath = xpath.encode('ascii')
|
xpath = xpath.encode('ascii')
|
||||||
|
|
||||||
n = node.find(xpath)
|
n = node.find(xpath)
|
||||||
if n is None:
|
if n is None or n.text is None:
|
||||||
if fatal:
|
if fatal:
|
||||||
name = xpath if name is None else name
|
name = xpath if name is None else name
|
||||||
raise ExtractorError('Could not find XML element %s' % name)
|
raise ExtractorError('Could not find XML element %s' % name)
|
||||||
@@ -644,17 +644,19 @@ def parse_iso8601(date_str, delimiter='T'):
|
|||||||
return calendar.timegm(dt.timetuple())
|
return calendar.timegm(dt.timetuple())
|
||||||
|
|
||||||
|
|
||||||
def unified_strdate(date_str):
|
def unified_strdate(date_str, day_first=True):
|
||||||
"""Return a string with the date in the format YYYYMMDD"""
|
"""Return a string with the date in the format YYYYMMDD"""
|
||||||
|
|
||||||
if date_str is None:
|
if date_str is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
upload_date = None
|
upload_date = None
|
||||||
# Replace commas
|
# Replace commas
|
||||||
date_str = date_str.replace(',', ' ')
|
date_str = date_str.replace(',', ' ')
|
||||||
# %z (UTC offset) is only supported in python>=3.2
|
# %z (UTC offset) is only supported in python>=3.2
|
||||||
date_str = re.sub(r' ?(\+|-)[0-9]{2}:?[0-9]{2}$', '', date_str)
|
date_str = re.sub(r' ?(\+|-)[0-9]{2}:?[0-9]{2}$', '', date_str)
|
||||||
|
# Remove AM/PM + timezone
|
||||||
|
date_str = re.sub(r'(?i)\s*(?:AM|PM)\s+[A-Z]+', '', date_str)
|
||||||
|
|
||||||
format_expressions = [
|
format_expressions = [
|
||||||
'%d %B %Y',
|
'%d %B %Y',
|
||||||
'%d %b %Y',
|
'%d %b %Y',
|
||||||
@@ -669,7 +671,6 @@ def unified_strdate(date_str):
|
|||||||
'%d/%m/%Y',
|
'%d/%m/%Y',
|
||||||
'%d/%m/%y',
|
'%d/%m/%y',
|
||||||
'%Y/%m/%d %H:%M:%S',
|
'%Y/%m/%d %H:%M:%S',
|
||||||
'%d/%m/%Y %H:%M:%S',
|
|
||||||
'%Y-%m-%d %H:%M:%S',
|
'%Y-%m-%d %H:%M:%S',
|
||||||
'%Y-%m-%d %H:%M:%S.%f',
|
'%Y-%m-%d %H:%M:%S.%f',
|
||||||
'%d.%m.%Y %H:%M',
|
'%d.%m.%Y %H:%M',
|
||||||
@@ -681,6 +682,14 @@ def unified_strdate(date_str):
|
|||||||
'%Y-%m-%dT%H:%M:%S.%f',
|
'%Y-%m-%dT%H:%M:%S.%f',
|
||||||
'%Y-%m-%dT%H:%M',
|
'%Y-%m-%dT%H:%M',
|
||||||
]
|
]
|
||||||
|
if day_first:
|
||||||
|
format_expressions.extend([
|
||||||
|
'%d/%m/%Y %H:%M:%S',
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
format_expressions.extend([
|
||||||
|
'%m/%d/%Y %H:%M:%S',
|
||||||
|
])
|
||||||
for expression in format_expressions:
|
for expression in format_expressions:
|
||||||
try:
|
try:
|
||||||
upload_date = datetime.datetime.strptime(date_str, expression).strftime('%Y%m%d')
|
upload_date = datetime.datetime.strptime(date_str, expression).strftime('%Y%m%d')
|
||||||
@@ -712,8 +721,10 @@ def date_from_str(date_str):
|
|||||||
Return a datetime object from a string in the format YYYYMMDD or
|
Return a datetime object from a string in the format YYYYMMDD or
|
||||||
(now|today)[+-][0-9](day|week|month|year)(s)?"""
|
(now|today)[+-][0-9](day|week|month|year)(s)?"""
|
||||||
today = datetime.date.today()
|
today = datetime.date.today()
|
||||||
if date_str == 'now'or date_str == 'today':
|
if date_str in ('now', 'today'):
|
||||||
return today
|
return today
|
||||||
|
if date_str == 'yesterday':
|
||||||
|
return today - datetime.timedelta(days=1)
|
||||||
match = re.match('(now|today)(?P<sign>[+-])(?P<time>\d+)(?P<unit>day|week|month|year)(s)?', date_str)
|
match = re.match('(now|today)(?P<sign>[+-])(?P<time>\d+)(?P<unit>day|week|month|year)(s)?', date_str)
|
||||||
if match is not None:
|
if match is not None:
|
||||||
sign = match.group('sign')
|
sign = match.group('sign')
|
||||||
@@ -808,22 +819,22 @@ def _windows_write_string(s, out):
|
|||||||
|
|
||||||
GetStdHandle = ctypes.WINFUNCTYPE(
|
GetStdHandle = ctypes.WINFUNCTYPE(
|
||||||
ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD)(
|
ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD)(
|
||||||
("GetStdHandle", ctypes.windll.kernel32))
|
(b"GetStdHandle", ctypes.windll.kernel32))
|
||||||
h = GetStdHandle(WIN_OUTPUT_IDS[fileno])
|
h = GetStdHandle(WIN_OUTPUT_IDS[fileno])
|
||||||
|
|
||||||
WriteConsoleW = ctypes.WINFUNCTYPE(
|
WriteConsoleW = ctypes.WINFUNCTYPE(
|
||||||
ctypes.wintypes.BOOL, ctypes.wintypes.HANDLE, ctypes.wintypes.LPWSTR,
|
ctypes.wintypes.BOOL, ctypes.wintypes.HANDLE, ctypes.wintypes.LPWSTR,
|
||||||
ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD),
|
ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD),
|
||||||
ctypes.wintypes.LPVOID)(("WriteConsoleW", ctypes.windll.kernel32))
|
ctypes.wintypes.LPVOID)((b"WriteConsoleW", ctypes.windll.kernel32))
|
||||||
written = ctypes.wintypes.DWORD(0)
|
written = ctypes.wintypes.DWORD(0)
|
||||||
|
|
||||||
GetFileType = ctypes.WINFUNCTYPE(ctypes.wintypes.DWORD, ctypes.wintypes.DWORD)(("GetFileType", ctypes.windll.kernel32))
|
GetFileType = ctypes.WINFUNCTYPE(ctypes.wintypes.DWORD, ctypes.wintypes.DWORD)((b"GetFileType", ctypes.windll.kernel32))
|
||||||
FILE_TYPE_CHAR = 0x0002
|
FILE_TYPE_CHAR = 0x0002
|
||||||
FILE_TYPE_REMOTE = 0x8000
|
FILE_TYPE_REMOTE = 0x8000
|
||||||
GetConsoleMode = ctypes.WINFUNCTYPE(
|
GetConsoleMode = ctypes.WINFUNCTYPE(
|
||||||
ctypes.wintypes.BOOL, ctypes.wintypes.HANDLE,
|
ctypes.wintypes.BOOL, ctypes.wintypes.HANDLE,
|
||||||
ctypes.POINTER(ctypes.wintypes.DWORD))(
|
ctypes.POINTER(ctypes.wintypes.DWORD))(
|
||||||
("GetConsoleMode", ctypes.windll.kernel32))
|
(b"GetConsoleMode", ctypes.windll.kernel32))
|
||||||
INVALID_HANDLE_VALUE = ctypes.wintypes.DWORD(-1).value
|
INVALID_HANDLE_VALUE = ctypes.wintypes.DWORD(-1).value
|
||||||
|
|
||||||
def not_a_console(handle):
|
def not_a_console(handle):
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
__version__ = '2014.12.10.2'
|
__version__ = '2014.12.12.2'
|
||||||
|
Reference in New Issue
Block a user