Compare commits

..

39 Commits

Author SHA1 Message Date
76e461f395 release 2014.02.13 2014-02-13 19:13:05 +01:00
1074982e6e [vesti] Add support for vesti.ru videos and live streams (Closes #2376) 2014-02-13 23:23:48 +07:00
29b2aaf035 [jadorecettepub] Remove unused import 2014-02-13 16:33:12 +01:00
6f90d098c5 [ecapist] modernize and fix id property 2014-02-13 16:32:42 +01:00
0715161450 Merge pull request #2373 from pulpe/_description_fixes
[collegehumor, chilloutzone] changed description in tests
2014-02-12 06:22:03 -08:00
896583517f [collegehumor, chilloutzone] changed description in tests 2014-02-12 15:11:57 +01:00
713d31fac8 [gametrailers] Fix gametrailers test 2014-02-12 01:50:53 +07:00
96cb10a5f5 [mtv] Improve title extraction 2014-02-12 01:07:30 +07:00
c207c1044e Merge pull request #2372 from pulpe/dropbox_fix
[dropbox] replace not working test
2014-02-11 09:34:49 -08:00
79629ec717 [dropbox] replace not working test 2014-02-11 17:27:36 +01:00
008fda0f08 [ndr] Replace 404 video test 2014-02-11 21:21:05 +07:00
0ae6b01937 [cnn] Add an extractor for blogs (closes #2361) 2014-02-11 14:38:17 +01:00
def630e523 [xtube] Fix uploader extraction 2014-02-11 14:20:41 +01:00
c5ba203e23 [xtube] use unicode_literals 2014-02-11 13:51:37 +01:00
2317e6b2b3 [yahoo] use unicode_literals 2014-02-11 13:51:23 +01:00
cb38928974 [firsttv] Skip test 2014-02-11 10:26:52 +07:00
fa78f13302 [streamcz] Minor changes 2014-02-11 10:19:02 +07:00
18395217c4 Merge branch '_stream' of https://github.com/pulpe/youtube-dl into pulpe-_stream 2014-02-11 09:18:46 +07:00
34bd987811 [freesound] Modernize 2014-02-10 21:03:14 +01:00
af6ba6a1c4 [exfm] Modernize 2014-02-10 21:00:37 +01:00
85409a0c69 [dotsub] Modernize 2014-02-10 20:52:53 +01:00
ebfe352b62 [breakcom] Modernize 2014-02-10 20:48:46 +01:00
fde56d2f17 [howcast] Modernize 2014-02-10 20:45:17 +01:00
3501423dfe [googleplus] Modernize and simplify 2014-02-10 20:36:11 +01:00
0de668af51 [instagram] Modernize 2014-02-10 20:24:12 +01:00
2a584ea90a [firsttv] Fix video URL regex 2014-02-11 00:49:37 +07:00
0f6ed94a15 [firsttv] Add support for 1tv.ru videoarchive 2014-02-11 00:20:41 +07:00
bcb891e82b [lifenews] Minor improvements 2014-02-10 21:07:41 +07:00
ac6e4ca1ed [brightcove] Unescape html entities from the 'og:video' url property (fixes #2360) 2014-02-10 07:50:10 +01:00
2e20bba708 release 2014.02.10 2014-02-10 02:01:11 +01:00
e70dc1d14b [youtube] Correct a minor regex typo 2014-02-10 01:30:47 +01:00
0793a7b3c7 [StreamCZ] Add support for stream.cz 2014-02-09 18:37:12 +01:00
026fcc0495 Fix #2355 (date parsing with dashes) 2014-02-09 18:09:57 +01:00
81c2f20b53 [youtube] Correct invalid JSON (Fixes #2353) 2014-02-09 17:56:10 +01:00
1afe753462 [slideshare] Fix description extraction and modernize
The ‘og:description’  property doesn’t contain the full description
2014-02-09 14:23:19 +01:00
524c2c716a [bloomberg] Fix extraction of ooyala embed code 2014-02-09 14:11:45 +01:00
b542d4bbd7 [kontrtube] Add support for kontrtube.ru (Closes #2354) 2014-02-09 19:53:11 +07:00
17968e444c [bbc.co.uk] Fix TV episode test 2014-02-09 04:04:21 +07:00
2e3fd9ec2f [bbc.co.uk] Improve overall extractor structure, add subtitles support
(#2184)

Everything from http://www.bbc.co.uk/iplayer/ should be downloadable
now.
2014-02-09 04:00:49 +07:00
33 changed files with 903 additions and 377 deletions

View File

@ -127,6 +127,7 @@ class TestUtil(unittest.TestCase):
self.assertEqual(unified_strdate('8/7/2009'), '20090708') self.assertEqual(unified_strdate('8/7/2009'), '20090708')
self.assertEqual(unified_strdate('Dec 14, 2012'), '20121214') self.assertEqual(unified_strdate('Dec 14, 2012'), '20121214')
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')
def test_find_xpath_attr(self): def test_find_xpath_attr(self):
testxml = u'''<root> testxml = u'''<root>

View File

@ -32,7 +32,10 @@ from .clipfish import ClipfishIE
from .cliphunter import CliphunterIE from .cliphunter import CliphunterIE
from .clipsyndicate import ClipsyndicateIE from .clipsyndicate import ClipsyndicateIE
from .cmt import CMTIE from .cmt import CMTIE
from .cnn import CNNIE from .cnn import (
CNNIE,
CNNBlogsIE,
)
from .collegehumor import CollegeHumorIE from .collegehumor import CollegeHumorIE
from .comedycentral import ComedyCentralIE, ComedyCentralShowsIE from .comedycentral import ComedyCentralIE, ComedyCentralShowsIE
from .condenast import CondeNastIE from .condenast import CondeNastIE
@ -64,6 +67,7 @@ from .extremetube import ExtremeTubeIE
from .facebook import FacebookIE from .facebook import FacebookIE
from .faz import FazIE from .faz import FazIE
from .firstpost import FirstpostIE from .firstpost import FirstpostIE
from .firsttv import FirstTVIE
from .fktv import ( from .fktv import (
FKTVIE, FKTVIE,
FKTVPosteckeIE, FKTVPosteckeIE,
@ -115,6 +119,7 @@ from .keezmovies import KeezMoviesIE
from .khanacademy import KhanAcademyIE from .khanacademy import KhanAcademyIE
from .kickstarter import KickStarterIE from .kickstarter import KickStarterIE
from .keek import KeekIE from .keek import KeekIE
from .kontrtube import KontrTubeIE
from .la7 import LA7IE from .la7 import LA7IE
from .lifenews import LifeNewsIE from .lifenews import LifeNewsIE
from .liveleak import LiveLeakIE from .liveleak import LiveLeakIE
@ -203,6 +208,7 @@ from .stanfordoc import StanfordOpenClassroomIE
from .statigram import StatigramIE from .statigram import StatigramIE
from .steam import SteamIE from .steam import SteamIE
from .streamcloud import StreamcloudIE from .streamcloud import StreamcloudIE
from .streamcz import StreamCZIE
from .sztvhu import SztvHuIE from .sztvhu import SztvHuIE
from .teamcoco import TeamcocoIE from .teamcoco import TeamcocoIE
from .techtalks import TechTalksIE from .techtalks import TechTalksIE
@ -224,6 +230,7 @@ from .ustream import UstreamIE, UstreamChannelIE
from .vbox7 import Vbox7IE from .vbox7 import Vbox7IE
from .veehd import VeeHDIE from .veehd import VeeHDIE
from .veoh import VeohIE from .veoh import VeohIE
from .vesti import VestiIE
from .vevo import VevoIE from .vevo import VevoIE
from .vice import ViceIE from .vice import ViceIE
from .viddler import ViddlerIE from .viddler import ViddlerIE

View File

@ -2,29 +2,160 @@ from __future__ import unicode_literals
import re import re
from .common import InfoExtractor from .subtitles import SubtitlesInfoExtractor
from ..utils import ExtractorError from ..utils import ExtractorError
class BBCCoUkIE(InfoExtractor): class BBCCoUkIE(SubtitlesInfoExtractor):
IE_NAME = 'bbc.co.uk' IE_NAME = 'bbc.co.uk'
IE_DESC = 'BBC - iPlayer Radio' IE_DESC = 'BBC iPlayer'
_VALID_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:programmes|iplayer/episode)/(?P<id>[\da-z]{8})' _VALID_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:programmes|iplayer/episode)/(?P<id>[\da-z]{8})'
_TEST = { _TESTS = [
'url': 'http://www.bbc.co.uk/programmes/p01q7wz1', {
'info_dict': { 'url': 'http://www.bbc.co.uk/programmes/p01q7wz1',
'id': 'p01q7wz4', 'info_dict': {
'ext': 'flv', 'id': 'p01q7wz4',
'title': 'Friction: Blu Mar Ten guest mix: Blu Mar Ten - Guest Mix', 'ext': 'flv',
'description': 'Blu Mar Ten deliver a Guest Mix for Friction.', 'title': 'Friction: Blu Mar Ten guest mix: Blu Mar Ten - Guest Mix',
'duration': 1936, 'description': 'Blu Mar Ten deliver a Guest Mix for Friction.',
'duration': 1936,
},
'params': {
# rtmp download
'skip_download': True,
}
}, },
'params': { {
# rtmp download 'url': 'http://www.bbc.co.uk/iplayer/episode/b00yng5w/The_Man_in_Black_Series_3_The_Printed_Name/',
'skip_download': True, 'info_dict': {
'id': 'b00yng1d',
'ext': 'flv',
'title': 'The Man in Black: Series 3: The Printed Name',
'description': "Mark Gatiss introduces Nicholas Pierpan's chilling tale of a writer's devilish pact with a mysterious man. Stars Ewan Bailey.",
'duration': 1800,
},
'params': {
# rtmp download
'skip_download': True,
}
},
{
'url': 'http://www.bbc.co.uk/iplayer/episode/b03vhd1f/The_Voice_UK_Series_3_Blind_Auditions_5/',
'info_dict': {
'id': 'b00yng1d',
'ext': 'flv',
'title': 'The Voice UK: Series 3: Blind Auditions 5',
'description': "Emma Willis and Marvin Humes present the fifth set of blind auditions in the singing competition, as the coaches continue to build their teams based on voice alone.",
'duration': 5100,
},
'params': {
# rtmp download
'skip_download': True,
},
'skip': 'Currently BBC iPlayer TV programmes are available to play in the UK only',
} }
} ]
def _extract_asx_playlist(self, connection, programme_id):
asx = self._download_xml(connection.get('href'), programme_id, 'Downloading ASX playlist')
return [ref.get('href') for ref in asx.findall('./Entry/ref')]
def _extract_connection(self, connection, programme_id):
formats = []
protocol = connection.get('protocol')
supplier = connection.get('supplier')
if protocol == 'http':
href = connection.get('href')
# ASX playlist
if supplier == 'asx':
for i, ref in enumerate(self._extract_asx_playlist(connection, programme_id)):
formats.append({
'url': ref,
'format_id': 'ref%s_%s' % (i, supplier),
})
# Direct link
else:
formats.append({
'url': href,
'format_id': supplier,
})
elif protocol == 'rtmp':
application = connection.get('application', 'ondemand')
auth_string = connection.get('authString')
identifier = connection.get('identifier')
server = connection.get('server')
formats.append({
'url': '%s://%s/%s?%s' % (protocol, server, application, auth_string),
'play_path': identifier,
'app': '%s?%s' % (application, auth_string),
'page_url': 'http://www.bbc.co.uk',
'player_url': 'http://www.bbc.co.uk/emp/releases/iplayer/revisions/617463_618125_4/617463_618125_4_emp.swf',
'rtmp_live': False,
'ext': 'flv',
'format_id': supplier,
})
return formats
def _extract_items(self, playlist):
return playlist.findall('./{http://bbc.co.uk/2008/emp/playlist}item')
def _extract_medias(self, media_selection):
return media_selection.findall('./{http://bbc.co.uk/2008/mp/mediaselection}media')
def _extract_connections(self, media):
return media.findall('./{http://bbc.co.uk/2008/mp/mediaselection}connection')
def _extract_video(self, media, programme_id):
formats = []
vbr = int(media.get('bitrate'))
vcodec = media.get('encoding')
service = media.get('service')
width = int(media.get('width'))
height = int(media.get('height'))
file_size = int(media.get('media_file_size'))
for connection in self._extract_connections(media):
conn_formats = self._extract_connection(connection, programme_id)
for format in conn_formats:
format.update({
'format_id': '%s_%s' % (service, format['format_id']),
'width': width,
'height': height,
'vbr': vbr,
'vcodec': vcodec,
'filesize': file_size,
})
formats.extend(conn_formats)
return formats
def _extract_audio(self, media, programme_id):
formats = []
abr = int(media.get('bitrate'))
acodec = media.get('encoding')
service = media.get('service')
for connection in self._extract_connections(media):
conn_formats = self._extract_connection(connection, programme_id)
for format in conn_formats:
format.update({
'format_id': '%s_%s' % (service, format['format_id']),
'abr': abr,
'acodec': acodec,
})
formats.extend(conn_formats)
return formats
def _extract_captions(self, media, programme_id):
subtitles = {}
for connection in self._extract_connections(media):
captions = self._download_xml(connection.get('href'), programme_id, 'Downloading captions')
lang = captions.get('{http://www.w3.org/XML/1998/namespace}lang', 'en')
ps = captions.findall('./{0}body/{0}div/{0}p'.format('{http://www.w3.org/2006/10/ttaf1}'))
srt = ''
for pos, p in enumerate(ps):
srt += '%s\r\n%s --> %s\r\n%s\r\n\r\n' % (str(pos), p.get('begin'), p.get('end'),
p.text.strip() if p.text is not None else '')
subtitles[lang] = srt
return subtitles
def _real_extract(self, url): def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
@ -33,84 +164,54 @@ class BBCCoUkIE(InfoExtractor):
playlist = self._download_xml('http://www.bbc.co.uk/iplayer/playlist/%s' % group_id, group_id, playlist = self._download_xml('http://www.bbc.co.uk/iplayer/playlist/%s' % group_id, group_id,
'Downloading playlist XML') 'Downloading playlist XML')
item = playlist.find('./{http://bbc.co.uk/2008/emp/playlist}item') no_items = playlist.find('./{http://bbc.co.uk/2008/emp/playlist}noItems')
if item is None: if no_items is not None:
no_items = playlist.find('./{http://bbc.co.uk/2008/emp/playlist}noItems') reason = no_items.get('reason')
if no_items is not None: if reason == 'preAvailability':
reason = no_items.get('reason') msg = 'Episode %s is not yet available' % group_id
if reason == 'preAvailability': elif reason == 'postAvailability':
msg = 'Episode %s is not yet available' % group_id msg = 'Episode %s is no longer available' % group_id
elif reason == 'postAvailability': else:
msg = 'Episode %s is no longer available' % group_id msg = 'Episode %s is not available: %s' % (group_id, reason)
else: raise ExtractorError(msg, expected=True)
msg = 'Episode %s is not available: %s' % (group_id, reason)
raise ExtractorError(msg, expected=True)
raise ExtractorError('Failed to extract media for episode %s' % group_id, expected=True)
title = playlist.find('./{http://bbc.co.uk/2008/emp/playlist}title').text
description = playlist.find('./{http://bbc.co.uk/2008/emp/playlist}summary').text
radio_programme_id = item.get('identifier')
duration = int(item.get('duration'))
media_selection = self._download_xml(
'http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/pc/vpid/%s' % radio_programme_id,
radio_programme_id, 'Downloading media selection XML')
formats = [] formats = []
for media in media_selection.findall('./{http://bbc.co.uk/2008/mp/mediaselection}media'): subtitles = None
bitrate = int(media.get('bitrate'))
encoding = media.get('encoding') for item in self._extract_items(playlist):
service = media.get('service') kind = item.get('kind')
connection = media.find('./{http://bbc.co.uk/2008/mp/mediaselection}connection') if kind != 'programme' and kind != 'radioProgramme':
protocol = connection.get('protocol') continue
priority = connection.get('priority') title = playlist.find('./{http://bbc.co.uk/2008/emp/playlist}title').text
supplier = connection.get('supplier') description = playlist.find('./{http://bbc.co.uk/2008/emp/playlist}summary').text
if protocol == 'http':
href = connection.get('href') programme_id = item.get('identifier')
# ASX playlist duration = int(item.get('duration'))
if supplier == 'asx':
asx = self._download_xml(href, radio_programme_id, 'Downloading %s ASX playlist' % service) media_selection = self._download_xml(
for i, ref in enumerate(asx.findall('./Entry/ref')): 'http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/pc/vpid/%s' % programme_id,
formats.append({ programme_id, 'Downloading media selection XML')
'url': ref.get('href'),
'format_id': '%s_ref%s' % (service, i), for media in self._extract_medias(media_selection):
'abr': bitrate, kind = media.get('kind')
'acodec': encoding, if kind == 'audio':
'preference': priority, formats.extend(self._extract_audio(media, programme_id))
}) elif kind == 'video':
continue formats.extend(self._extract_video(media, programme_id))
# Direct link elif kind == 'captions':
formats.append({ subtitles = self._extract_captions(media, programme_id)
'url': href,
'format_id': service, if self._downloader.params.get('listsubtitles', False):
'abr': bitrate, self._list_available_subtitles(programme_id, subtitles)
'acodec': encoding, return
'preference': priority,
})
elif protocol == 'rtmp':
application = connection.get('application', 'ondemand')
auth_string = connection.get('authString')
identifier = connection.get('identifier')
server = connection.get('server')
formats.append({
'url': '%s://%s/%s?%s' % (protocol, server, application, auth_string),
'play_path': identifier,
'app': '%s?%s' % (application, auth_string),
'rtmp_live': False,
'ext': 'flv',
'format_id': service,
'abr': bitrate,
'acodec': encoding,
'preference': priority,
})
self._sort_formats(formats) self._sort_formats(formats)
return { return {
'id': radio_programme_id, 'id': programme_id,
'title': title, 'title': title,
'description': description, 'description': description,
'duration': duration, 'duration': duration,
'formats': formats, 'formats': formats,
'subtitles': subtitles,
} }

View File

@ -24,5 +24,7 @@ class BloombergIE(InfoExtractor):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
name = mobj.group('name') name = mobj.group('name')
webpage = self._download_webpage(url, name) webpage = self._download_webpage(url, name)
ooyala_url = self._twitter_search_player(webpage) embed_code = self._search_regex(
return self.url_result(ooyala_url, OoyalaIE.ie_key()) r'<source src="https?://[^/]+/[^/]+/[^/]+/([^/]+)', webpage,
'embed code')
return OoyalaIE._build_url_result(embed_code)

View File

@ -1,18 +1,20 @@
from __future__ import unicode_literals
import re import re
import json import json
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import determine_ext
class BreakIE(InfoExtractor): class BreakIE(InfoExtractor):
_VALID_URL = r'(?:http://)?(?:www\.)?break\.com/video/([^/]+)' _VALID_URL = r'http://(?:www\.)?break\.com/video/([^/]+)'
_TEST = { _TEST = {
u'url': u'http://www.break.com/video/when-girls-act-like-guys-2468056', 'url': 'http://www.break.com/video/when-girls-act-like-guys-2468056',
u'file': u'2468056.mp4', 'md5': 'a3513fb1547fba4fb6cfac1bffc6c46b',
u'md5': u'a3513fb1547fba4fb6cfac1bffc6c46b', 'info_dict': {
u'info_dict': { 'id': '2468056',
u"title": u"When Girls Act Like D-Bags" 'ext': 'mp4',
'title': 'When Girls Act Like D-Bags',
} }
} }
@ -22,17 +24,16 @@ class BreakIE(InfoExtractor):
embed_url = 'http://www.break.com/embed/%s' % video_id embed_url = 'http://www.break.com/embed/%s' % video_id
webpage = self._download_webpage(embed_url, video_id) webpage = self._download_webpage(embed_url, video_id)
info_json = self._search_regex(r'var embedVars = ({.*?});', webpage, info_json = self._search_regex(r'var embedVars = ({.*?});', webpage,
u'info json', flags=re.DOTALL) 'info json', flags=re.DOTALL)
info = json.loads(info_json) info = json.loads(info_json)
video_url = info['videoUri'] video_url = info['videoUri']
m_youtube = re.search(r'(https?://www\.youtube\.com/watch\?v=.*)', video_url) m_youtube = re.search(r'(https?://www\.youtube\.com/watch\?v=.*)', video_url)
if m_youtube is not None: if m_youtube is not None:
return self.url_result(m_youtube.group(1), 'Youtube') return self.url_result(m_youtube.group(1), 'Youtube')
final_url = video_url + '?' + info['AuthToken'] final_url = video_url + '?' + info['AuthToken']
return [{ return {
'id': video_id, 'id': video_id,
'url': final_url, 'url': final_url,
'ext': determine_ext(final_url), 'title': info['contentName'],
'title': info['contentName'],
'thumbnail': info['thumbUri'], 'thumbnail': info['thumbUri'],
}] }

View File

@ -17,6 +17,7 @@ from ..utils import (
ExtractorError, ExtractorError,
unsmuggle_url, unsmuggle_url,
unescapeHTML,
) )
@ -139,7 +140,7 @@ class BrightcoveIE(InfoExtractor):
url_m = re.search(r'<meta\s+property="og:video"\s+content="(http://c.brightcove.com/[^"]+)"', webpage) url_m = re.search(r'<meta\s+property="og:video"\s+content="(http://c.brightcove.com/[^"]+)"', webpage)
if url_m: if url_m:
return [url_m.group(1)] return [unescapeHTML(url_m.group(1))]
matches = re.findall( matches = re.findall(
r'''(?sx)<object r'''(?sx)<object

View File

@ -42,7 +42,7 @@ class ChilloutzoneIE(InfoExtractor):
'id': '85523671', 'id': '85523671',
'ext': 'mp4', 'ext': 'mp4',
'title': 'The Sunday Times - Icons', 'title': 'The Sunday Times - Icons',
'description': 'md5:3e5e8e839f076a637c6b9406c8f25c4c', 'description': 'md5:3e1c0dc6047498d6728dcdaad0891762',
'uploader': 'Us', 'uploader': 'Us',
'uploader_id': 'usfilms', 'uploader_id': 'usfilms',
'upload_date': '20140131' 'upload_date': '20140131'

View File

@ -6,6 +6,7 @@ from .common import InfoExtractor
from ..utils import ( from ..utils import (
int_or_none, int_or_none,
parse_duration, parse_duration,
url_basename,
) )
@ -98,3 +99,28 @@ class CNNIE(InfoExtractor):
'duration': duration, 'duration': duration,
'upload_date': upload_date, 'upload_date': upload_date,
} }
class CNNBlogsIE(InfoExtractor):
_VALID_URL = r'https?://[^\.]+\.blogs\.cnn\.com/.+'
_TEST = {
'url': 'http://reliablesources.blogs.cnn.com/2014/02/09/criminalizing-journalism/',
'md5': '3e56f97b0b6ffb4b79f4ea0749551084',
'info_dict': {
'id': 'bestoftv/2014/02/09/criminalizing-journalism.cnn',
'ext': 'mp4',
'title': 'Criminalizing journalism?',
'description': 'Glenn Greenwald responds to comments made this week on Capitol Hill that journalists could be criminal accessories.',
'upload_date': '20140209',
},
'add_ie': ['CNN'],
}
def _real_extract(self, url):
webpage = self._download_webpage(url, url_basename(url))
cnn_url = self._html_search_regex(r'data-url="(.+?)"', webpage, 'cnn url')
return {
'_type': 'url',
'url': cnn_url,
'ie_key': CNNIE.ie_key(),
}

View File

@ -42,7 +42,7 @@ class CollegeHumorIE(InfoExtractor):
'title': 'Funny Dogs Protecting Babies Compilation 2014 [NEW HD]', 'title': 'Funny Dogs Protecting Babies Compilation 2014 [NEW HD]',
'uploader': 'Funnyplox TV', 'uploader': 'Funnyplox TV',
'uploader_id': 'funnyploxtv', 'uploader_id': 'funnyploxtv',
'description': 'md5:11812366244110c3523968aa74f02521', 'description': 'md5:7ded37421526d54afdf005e25bc2b7a3',
'upload_date': '20140128', 'upload_date': '20140128',
}, },
'params': { 'params': {

View File

@ -271,8 +271,11 @@ class InfoExtractor(object):
def _download_json(self, url_or_request, video_id, def _download_json(self, url_or_request, video_id,
note=u'Downloading JSON metadata', note=u'Downloading JSON metadata',
errnote=u'Unable to download JSON metadata'): errnote=u'Unable to download JSON metadata',
transform_source=None):
json_string = self._download_webpage(url_or_request, video_id, note, errnote) json_string = self._download_webpage(url_or_request, video_id, note, errnote)
if transform_source:
json_string = transform_source(json_string)
try: try:
return json.loads(json_string) return json.loads(json_string)
except ValueError as ve: except ValueError as ve:

View File

@ -1,41 +1,42 @@
from __future__ import unicode_literals
import re import re
import json
import time import time
from .common import InfoExtractor from .common import InfoExtractor
class DotsubIE(InfoExtractor): class DotsubIE(InfoExtractor):
_VALID_URL = r'(?:http://)?(?:www\.)?dotsub\.com/view/([^/]+)' _VALID_URL = r'http://(?:www\.)?dotsub\.com/view/(?P<id>[^/]+)'
_TEST = { _TEST = {
u'url': u'http://dotsub.com/view/aed3b8b2-1889-4df5-ae63-ad85f5572f27', 'url': 'http://dotsub.com/view/aed3b8b2-1889-4df5-ae63-ad85f5572f27',
u'file': u'aed3b8b2-1889-4df5-ae63-ad85f5572f27.flv', 'md5': '0914d4d69605090f623b7ac329fea66e',
u'md5': u'0914d4d69605090f623b7ac329fea66e', 'info_dict': {
u'info_dict': { 'id': 'aed3b8b2-1889-4df5-ae63-ad85f5572f27',
u"title": u"Pyramids of Waste (2010), AKA The Lightbulb Conspiracy - Planned obsolescence documentary", 'ext': 'flv',
u"uploader": u"4v4l0n42", 'title': 'Pyramids of Waste (2010), AKA The Lightbulb Conspiracy - Planned obsolescence documentary',
u'description': u'Pyramids of Waste (2010) also known as "The lightbulb conspiracy" is a documentary about how our economic system based on consumerism and planned obsolescence is breaking our planet down.\r\n\r\nSolutions to this can be found at:\r\nhttp://robotswillstealyourjob.com\r\nhttp://www.federicopistono.org\r\n\r\nhttp://opensourceecology.org\r\nhttp://thezeitgeistmovement.com', 'uploader': '4v4l0n42',
u'thumbnail': u'http://dotsub.com/media/aed3b8b2-1889-4df5-ae63-ad85f5572f27/p', 'description': 'Pyramids of Waste (2010) also known as "The lightbulb conspiracy" is a documentary about how our economic system based on consumerism and planned obsolescence is breaking our planet down.\r\n\r\nSolutions to this can be found at:\r\nhttp://robotswillstealyourjob.com\r\nhttp://www.federicopistono.org\r\n\r\nhttp://opensourceecology.org\r\nhttp://thezeitgeistmovement.com',
u'upload_date': u'20101213', 'thumbnail': 'http://dotsub.com/media/aed3b8b2-1889-4df5-ae63-ad85f5572f27/p',
'upload_date': '20101213',
} }
} }
def _real_extract(self, url): def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
video_id = mobj.group(1) video_id = mobj.group('id')
info_url = "https://dotsub.com/api/media/%s/metadata" %(video_id) info_url = "https://dotsub.com/api/media/%s/metadata" % video_id
webpage = self._download_webpage(info_url, video_id) info = self._download_json(info_url, video_id)
info = json.loads(webpage)
date = time.gmtime(info['dateCreated']/1000) # The timestamp is in miliseconds date = time.gmtime(info['dateCreated']/1000) # The timestamp is in miliseconds
return [{ return {
'id': video_id, 'id': video_id,
'url': info['mediaURI'], 'url': info['mediaURI'],
'ext': 'flv', 'ext': 'flv',
'title': info['title'], 'title': info['title'],
'thumbnail': info['screenshotURI'], 'thumbnail': info['screenshotURI'],
'description': info['description'], 'description': info['description'],
'uploader': info['user'], 'uploader': info['user'],
'view_count': info['numberOfViews'], 'view_count': info['numberOfViews'],
'upload_date': u'%04i%02i%02i' % (date.tm_year, date.tm_mon, date.tm_mday), 'upload_date': '%04i%02i%02i' % (date.tm_year, date.tm_mon, date.tm_mday),
}] }

View File

@ -10,11 +10,12 @@ from .common import InfoExtractor
class DropboxIE(InfoExtractor): class DropboxIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?dropbox[.]com/s/(?P<id>[a-zA-Z0-9]{15})/(?P<title>[^?#]*)' _VALID_URL = r'https?://(?:www\.)?dropbox[.]com/s/(?P<id>[a-zA-Z0-9]{15})/(?P<title>[^?#]*)'
_TEST = { _TEST = {
'url': 'https://www.dropbox.com/s/mcnzehi9wo55th4/20131219_085616.mp4', 'url': 'https://www.dropbox.com/s/0qr9sai2veej4f8/THE_DOCTOR_GAMES.mp4',
'file': 'mcnzehi9wo55th4.mp4', 'md5': '8ae17c51172fb7f93bdd6a214cc8c896',
'md5': 'f6d65b1b326e82fd7ab7720bea3dacae',
'info_dict': { 'info_dict': {
'title': '20131219_085616' 'id': '0qr9sai2veej4f8',
'ext': 'mp4',
'title': 'THE_DOCTOR_GAMES'
} }
} }

View File

@ -1,9 +1,9 @@
import json from __future__ import unicode_literals
import re import re
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import ( from ..utils import (
compat_str,
compat_urllib_parse, compat_urllib_parse,
ExtractorError, ExtractorError,
@ -11,70 +11,68 @@ from ..utils import (
class EscapistIE(InfoExtractor): class EscapistIE(InfoExtractor):
_VALID_URL = r'^https?://?(www\.)?escapistmagazine\.com/videos/view/(?P<showname>[^/]+)/(?P<episode>[^/?]+)[/?]?.*$' _VALID_URL = r'^https?://?(www\.)?escapistmagazine\.com/videos/view/(?P<showname>[^/]+)/(?P<id>[0-9]+)-'
_TEST = { _TEST = {
u'url': u'http://www.escapistmagazine.com/videos/view/the-escapist-presents/6618-Breaking-Down-Baldurs-Gate', 'url': 'http://www.escapistmagazine.com/videos/view/the-escapist-presents/6618-Breaking-Down-Baldurs-Gate',
u'file': u'6618-Breaking-Down-Baldurs-Gate.mp4', 'md5': 'ab3a706c681efca53f0a35f1415cf0d1',
u'md5': u'ab3a706c681efca53f0a35f1415cf0d1', 'info_dict': {
u'info_dict': { 'id': '6618',
u"description": u"Baldur's Gate: Original, Modded or Enhanced Edition? I'll break down what you can expect from the new Baldur's Gate: Enhanced Edition.", 'ext': 'mp4',
u"uploader": u"the-escapist-presents", 'description': "Baldur's Gate: Original, Modded or Enhanced Edition? I'll break down what you can expect from the new Baldur's Gate: Enhanced Edition.",
u"title": u"Breaking Down Baldur's Gate" 'uploader': 'the-escapist-presents',
'title': "Breaking Down Baldur's Gate",
} }
} }
def _real_extract(self, url): def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
showName = mobj.group('showname') showName = mobj.group('showname')
videoId = mobj.group('episode') video_id = mobj.group('id')
self.report_extraction(videoId) self.report_extraction(video_id)
webpage = self._download_webpage(url, videoId) webpage = self._download_webpage(url, video_id)
videoDesc = self._html_search_regex( videoDesc = self._html_search_regex(
r'<meta name="description" content="([^"]*)"', r'<meta name="description" content="([^"]*)"',
webpage, u'description', fatal=False) webpage, 'description', fatal=False)
playerUrl = self._og_search_video_url(webpage, name=u'player URL') playerUrl = self._og_search_video_url(webpage, name=u'player URL')
title = self._html_search_regex( title = self._html_search_regex(
r'<meta name="title" content="([^"]*)"', r'<meta name="title" content="([^"]*)"',
webpage, u'title').split(' : ')[-1] webpage, 'title').split(' : ')[-1]
configUrl = self._search_regex('config=(.*)$', playerUrl, u'config URL') configUrl = self._search_regex('config=(.*)$', playerUrl, 'config URL')
configUrl = compat_urllib_parse.unquote(configUrl) configUrl = compat_urllib_parse.unquote(configUrl)
formats = [] formats = []
def _add_format(name, cfgurl): def _add_format(name, cfgurl, quality):
configJSON = self._download_webpage( config = self._download_json(
cfgurl, videoId, cfgurl, video_id,
u'Downloading ' + name + ' configuration', 'Downloading ' + name + ' configuration',
u'Unable to download ' + name + ' configuration') 'Unable to download ' + name + ' configuration',
transform_source=lambda s: s.replace("'", '"'))
# Technically, it's JavaScript, not JSON
configJSON = configJSON.replace("'", '"')
try:
config = json.loads(configJSON)
except (ValueError,) as err:
raise ExtractorError(u'Invalid JSON in configuration file: ' + compat_str(err))
playlist = config['playlist'] playlist = config['playlist']
formats.append({ formats.append({
'url': playlist[1]['url'], 'url': playlist[1]['url'],
'format_id': name, 'format_id': name,
'quality': quality,
}) })
_add_format(u'normal', configUrl) _add_format('normal', configUrl, quality=0)
hq_url = (configUrl + hq_url = (configUrl +
('&hq=1' if '?' in configUrl else configUrl + '?hq=1')) ('&hq=1' if '?' in configUrl else configUrl + '?hq=1'))
try: try:
_add_format(u'hq', hq_url) _add_format('hq', hq_url, quality=1)
except ExtractorError: except ExtractorError:
pass # That's fine, we'll just use normal quality pass # That's fine, we'll just use normal quality
self._sort_formats(formats)
return { return {
'id': videoId, 'id': video_id,
'formats': formats, 'formats': formats,
'uploader': showName, 'uploader': showName,
'title': title, 'title': title,

View File

@ -1,56 +1,58 @@
from __future__ import unicode_literals
import re import re
import json
from .common import InfoExtractor from .common import InfoExtractor
class ExfmIE(InfoExtractor): class ExfmIE(InfoExtractor):
IE_NAME = u'exfm' IE_NAME = 'exfm'
IE_DESC = u'ex.fm' IE_DESC = 'ex.fm'
_VALID_URL = r'(?:http://)?(?:www\.)?ex\.fm/song/([^/]+)' _VALID_URL = r'http://(?:www\.)?ex\.fm/song/(?P<id>[^/]+)'
_SOUNDCLOUD_URL = r'(?:http://)?(?:www\.)?api\.soundcloud\.com/tracks/([^/]+)/stream' _SOUNDCLOUD_URL = r'http://(?:www\.)?api\.soundcloud\.com/tracks/([^/]+)/stream'
_TESTS = [ _TESTS = [
{ {
u'url': u'http://ex.fm/song/eh359', 'url': 'http://ex.fm/song/eh359',
u'file': u'44216187.mp3', 'md5': 'e45513df5631e6d760970b14cc0c11e7',
u'md5': u'e45513df5631e6d760970b14cc0c11e7', 'info_dict': {
u'info_dict': { 'id': '44216187',
u"title": u"Test House \"Love Is Not Enough\" (Extended Mix) DeadJournalist Exclusive", 'ext': 'mp3',
u"uploader": u"deadjournalist", 'title': 'Test House "Love Is Not Enough" (Extended Mix) DeadJournalist Exclusive',
u'upload_date': u'20120424', 'uploader': 'deadjournalist',
u'description': u'Test House \"Love Is Not Enough\" (Extended Mix) DeadJournalist Exclusive', 'upload_date': '20120424',
'description': 'Test House \"Love Is Not Enough\" (Extended Mix) DeadJournalist Exclusive',
}, },
u'note': u'Soundcloud song', 'note': 'Soundcloud song',
u'skip': u'The site is down too often', 'skip': 'The site is down too often',
}, },
{ {
u'url': u'http://ex.fm/song/wddt8', 'url': 'http://ex.fm/song/wddt8',
u'file': u'wddt8.mp3', 'md5': '966bd70741ac5b8570d8e45bfaed3643',
u'md5': u'966bd70741ac5b8570d8e45bfaed3643', 'info_dict': {
u'info_dict': { 'id': 'wddt8',
u'title': u'Safe and Sound', 'ext': 'mp3',
u'uploader': u'Capital Cities', 'title': 'Safe and Sound',
'uploader': 'Capital Cities',
}, },
u'skip': u'The site is down too often', 'skip': 'The site is down too often',
}, },
] ]
def _real_extract(self, url): def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
song_id = mobj.group(1) song_id = mobj.group('id')
info_url = "http://ex.fm/api/v3/song/%s" %(song_id) info_url = "http://ex.fm/api/v3/song/%s" % song_id
webpage = self._download_webpage(info_url, song_id) info = self._download_json(info_url, song_id)['song']
info = json.loads(webpage) song_url = info['url']
song_url = info['song']['url']
if re.match(self._SOUNDCLOUD_URL, song_url) is not None: if re.match(self._SOUNDCLOUD_URL, song_url) is not None:
self.to_screen('Soundcloud song detected') self.to_screen('Soundcloud song detected')
return self.url_result(song_url.replace('/stream',''), 'Soundcloud') return self.url_result(song_url.replace('/stream', ''), 'Soundcloud')
return [{ return {
'id': song_id, 'id': song_id,
'url': song_url, 'url': song_url,
'ext': 'mp3', 'ext': 'mp3',
'title': info['song']['title'], 'title': info['title'],
'thumbnail': info['song']['image']['large'], 'thumbnail': info['image']['large'],
'uploader': info['song']['artist'], 'uploader': info['artist'],
'view_count': info['song']['loved_count'], 'view_count': info['loved_count'],
}] }

View File

@ -0,0 +1,60 @@
# encoding: utf-8
from __future__ import unicode_literals
import re
from .common import InfoExtractor
from ..utils import int_or_none
class FirstTVIE(InfoExtractor):
IE_NAME = 'firsttv'
IE_DESC = 'Видеоархив - Первый канал'
_VALID_URL = r'http://(?:www\.)?1tv\.ru/videoarchive/(?P<id>\d+)'
_TEST = {
'url': 'http://www.1tv.ru/videoarchive/73390',
'md5': '3de6390cf0cca4a5eae1d1d83895e5ad',
'info_dict': {
'id': '73390',
'ext': 'mp4',
'title': 'Олимпийские канатные дороги',
'description': 'md5:cc730d2bf4215463e37fff6a1e277b13',
'thumbnail': 'http://img1.1tv.ru/imgsize640x360/PR20140210114657.JPG',
'duration': 149,
},
'skip': 'Only works from Russia',
}
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
video_id = mobj.group('id')
webpage = self._download_webpage(url, video_id, 'Downloading page')
video_url = self._html_search_regex(
r'''(?s)jwplayer\('flashvideoportal_1'\)\.setup\({.*?'file': '([^']+)'.*?}\);''', webpage, 'video URL')
title = self._html_search_regex(
r'<div class="tv_translation">\s*<h1><a href="[^"]+">([^<]*)</a>', webpage, 'title')
description = self._html_search_regex(
r'<div class="descr">\s*<div>&nbsp;</div>\s*<p>([^<]*)</p></div>', webpage, 'description', fatal=False)
thumbnail = self._og_search_thumbnail(webpage)
duration = self._og_search_property('video:duration', webpage, 'video duration', fatal=False)
like_count = self._html_search_regex(r'title="Понравилось".*?/></label> \[(\d+)\]',
webpage, 'like count', fatal=False)
dislike_count = self._html_search_regex(r'title="Не понравилось".*?/></label> \[(\d+)\]',
webpage, 'dislike count', fatal=False)
return {
'id': video_id,
'url': video_url,
'thumbnail': thumbnail,
'title': title,
'description': description,
'duration': int_or_none(duration),
'like_count': int_or_none(like_count),
'dislike_count': int_or_none(dislike_count),
}

View File

@ -1,18 +1,21 @@
from __future__ import unicode_literals
import re import re
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import determine_ext
class FreesoundIE(InfoExtractor): class FreesoundIE(InfoExtractor):
_VALID_URL = r'(?:https?://)?(?:www\.)?freesound\.org/people/([^/]+)/sounds/(?P<id>[^/]+)' _VALID_URL = r'https?://(?:www\.)?freesound\.org/people/([^/]+)/sounds/(?P<id>[^/]+)'
_TEST = { _TEST = {
u'url': u'http://www.freesound.org/people/miklovan/sounds/194503/', 'url': 'http://www.freesound.org/people/miklovan/sounds/194503/',
u'file': u'194503.mp3', 'md5': '12280ceb42c81f19a515c745eae07650',
u'md5': u'12280ceb42c81f19a515c745eae07650', 'info_dict': {
u'info_dict': { 'id': '194503',
u"title": u"gulls in the city.wav", 'ext': 'mp3',
u"uploader" : u"miklovan", 'title': 'gulls in the city.wav',
u'description': u'the sounds of seagulls in the city', 'uploader': 'miklovan',
'description': 'the sounds of seagulls in the city',
} }
} }
@ -20,17 +23,17 @@ class FreesoundIE(InfoExtractor):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
music_id = mobj.group('id') music_id = mobj.group('id')
webpage = self._download_webpage(url, music_id) webpage = self._download_webpage(url, music_id)
title = self._html_search_regex(r'<div id="single_sample_header">.*?<a href="#">(.+?)</a>', title = self._html_search_regex(
webpage, 'music title', flags=re.DOTALL) r'<div id="single_sample_header">.*?<a href="#">(.+?)</a>',
music_url = self._og_search_property('audio', webpage, 'music url') webpage, 'music title', flags=re.DOTALL)
description = self._html_search_regex(r'<div id="sound_description">(.*?)</div>', description = self._html_search_regex(
webpage, 'description', fatal=False, flags=re.DOTALL) r'<div id="sound_description">(.*?)</div>', webpage, 'description',
fatal=False, flags=re.DOTALL)
return [{ return {
'id': music_id, 'id': music_id,
'title': title, 'title': title,
'url': music_url, 'url': self._og_search_property('audio', webpage, 'music url'),
'uploader': self._og_search_property('audio:artist', webpage, 'music uploader'), 'uploader': self._og_search_property('audio:artist', webpage, 'music uploader'),
'ext': determine_ext(music_url),
'description': description, 'description': description,
}] }

View File

@ -7,10 +7,11 @@ class GametrailersIE(MTVServicesInfoExtractor):
_VALID_URL = r'http://www\.gametrailers\.com/(?P<type>videos|reviews|full-episodes)/(?P<id>.*?)/(?P<title>.*)' _VALID_URL = r'http://www\.gametrailers\.com/(?P<type>videos|reviews|full-episodes)/(?P<id>.*?)/(?P<title>.*)'
_TEST = { _TEST = {
'url': 'http://www.gametrailers.com/videos/zbvr8i/mirror-s-edge-2-e3-2013--debut-trailer', 'url': 'http://www.gametrailers.com/videos/zbvr8i/mirror-s-edge-2-e3-2013--debut-trailer',
'file': '70e9a5d7-cf25-4a10-9104-6f3e7342ae0d.mp4',
'md5': '4c8e67681a0ea7ec241e8c09b3ea8cf7', 'md5': '4c8e67681a0ea7ec241e8c09b3ea8cf7',
'info_dict': { 'info_dict': {
'title': 'Mirror\'s Edge 2|E3 2013: Debut Trailer', 'id': '70e9a5d7-cf25-4a10-9104-6f3e7342ae0d',
'ext': 'mp4',
'title': 'E3 2013: Debut Trailer',
'description': 'Faith is back! Check out the World Premiere trailer for Mirror\'s Edge 2 straight from the EA Press Conference at E3 2013!', 'description': 'Faith is back! Check out the World Premiere trailer for Mirror\'s Edge 2 straight from the EA Press Conference at E3 2013!',
}, },
} }

View File

@ -1,4 +1,5 @@
# coding: utf-8 # coding: utf-8
from __future__ import unicode_literals
import datetime import datetime
import re import re
@ -10,32 +11,28 @@ from ..utils import (
class GooglePlusIE(InfoExtractor): class GooglePlusIE(InfoExtractor):
IE_DESC = u'Google Plus' IE_DESC = 'Google Plus'
_VALID_URL = r'(?:https://)?plus\.google\.com/(?:[^/]+/)*?posts/(\w+)' _VALID_URL = r'https://plus\.google\.com/(?:[^/]+/)*?posts/(?P<id>\w+)'
IE_NAME = u'plus.google' IE_NAME = 'plus.google'
_TEST = { _TEST = {
u"url": u"https://plus.google.com/u/0/108897254135232129896/posts/ZButuJc6CtH", 'url': 'https://plus.google.com/u/0/108897254135232129896/posts/ZButuJc6CtH',
u"file": u"ZButuJc6CtH.flv", 'info_dict': {
u"info_dict": { 'id': 'ZButuJc6CtH',
u"upload_date": u"20120613", 'ext': 'flv',
u"uploader": u"井上ヨシマサ", 'upload_date': '20120613',
u"title": u"嘆きの天使 降臨" 'uploader': '井上ヨシマサ',
'title': '嘆きの天使 降臨',
} }
} }
def _real_extract(self, url): def _real_extract(self, url):
# Extract id from URL # Extract id from URL
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
if mobj is None:
raise ExtractorError(u'Invalid URL: %s' % url)
post_url = mobj.group(0) video_id = mobj.group('id')
video_id = mobj.group(1)
video_extension = 'flv'
# Step 1, Retrieve post webpage to extract further information # Step 1, Retrieve post webpage to extract further information
webpage = self._download_webpage(post_url, video_id, u'Downloading entry webpage') webpage = self._download_webpage(url, video_id, 'Downloading entry webpage')
self.report_extraction(video_id) self.report_extraction(video_id)
@ -43,7 +40,7 @@ class GooglePlusIE(InfoExtractor):
upload_date = self._html_search_regex( upload_date = self._html_search_regex(
r'''(?x)<a.+?class="o-U-s\s[^"]+"\s+style="display:\s*none"\s*> r'''(?x)<a.+?class="o-U-s\s[^"]+"\s+style="display:\s*none"\s*>
([0-9]{4}-[0-9]{2}-[0-9]{2})</a>''', ([0-9]{4}-[0-9]{2}-[0-9]{2})</a>''',
webpage, u'upload date', fatal=False, flags=re.VERBOSE) webpage, 'upload date', fatal=False, flags=re.VERBOSE)
if upload_date: if upload_date:
# Convert timestring to a format suitable for filename # Convert timestring to a format suitable for filename
upload_date = datetime.datetime.strptime(upload_date, "%Y-%m-%d") upload_date = datetime.datetime.strptime(upload_date, "%Y-%m-%d")
@ -51,28 +48,27 @@ class GooglePlusIE(InfoExtractor):
# Extract uploader # Extract uploader
uploader = self._html_search_regex(r'rel\="author".*?>(.*?)</a>', uploader = self._html_search_regex(r'rel\="author".*?>(.*?)</a>',
webpage, u'uploader', fatal=False) webpage, 'uploader', fatal=False)
# Extract title # Extract title
# Get the first line for title # Get the first line for title
video_title = self._html_search_regex(r'<meta name\=\"Description\" content\=\"(.*?)[\n<"]', video_title = self._html_search_regex(r'<meta name\=\"Description\" content\=\"(.*?)[\n<"]',
webpage, 'title', default=u'NA') webpage, 'title', default='NA')
# Step 2, Simulate clicking the image box to launch video # Step 2, Simulate clicking the image box to launch video
DOMAIN = 'https://plus.google.com/' DOMAIN = 'https://plus.google.com/'
video_page = self._search_regex(r'<a href="((?:%s)?photos/.*?)"' % re.escape(DOMAIN), video_page = self._search_regex(r'<a href="((?:%s)?photos/.*?)"' % re.escape(DOMAIN),
webpage, u'video page URL') webpage, 'video page URL')
if not video_page.startswith(DOMAIN): if not video_page.startswith(DOMAIN):
video_page = DOMAIN + video_page video_page = DOMAIN + video_page
webpage = self._download_webpage(video_page, video_id, u'Downloading video page') webpage = self._download_webpage(video_page, video_id, 'Downloading video page')
# Extract video links on video page # Extract video links all sizes
"""Extract video links of all sizes"""
pattern = r'\d+,\d+,(\d+),"(http\://redirector\.googlevideo\.com.*?)"' pattern = r'\d+,\d+,(\d+),"(http\://redirector\.googlevideo\.com.*?)"'
mobj = re.findall(pattern, webpage) mobj = re.findall(pattern, webpage)
if len(mobj) == 0: if len(mobj) == 0:
raise ExtractorError(u'Unable to extract video links') raise ExtractorError('Unable to extract video links')
# Sort in resolution # Sort in resolution
links = sorted(mobj) links = sorted(mobj)
@ -87,12 +83,11 @@ class GooglePlusIE(InfoExtractor):
except AttributeError: # Python 3 except AttributeError: # Python 3
video_url = bytes(video_url, 'ascii').decode('unicode-escape') video_url = bytes(video_url, 'ascii').decode('unicode-escape')
return {
return [{ 'id': video_id,
'id': video_id, 'url': video_url,
'url': video_url,
'uploader': uploader, 'uploader': uploader,
'upload_date': upload_date, 'upload_date': upload_date,
'title': video_title, 'title': video_title,
'ext': video_extension, 'ext': 'flv',
}] }

View File

@ -1,17 +1,20 @@
from __future__ import unicode_literals
import re import re
from .common import InfoExtractor from .common import InfoExtractor
class HowcastIE(InfoExtractor): class HowcastIE(InfoExtractor):
_VALID_URL = r'(?:https?://)?(?:www\.)?howcast\.com/videos/(?P<id>\d+)' _VALID_URL = r'https?://(?:www\.)?howcast\.com/videos/(?P<id>\d+)'
_TEST = { _TEST = {
u'url': u'http://www.howcast.com/videos/390161-How-to-Tie-a-Square-Knot-Properly', 'url': 'http://www.howcast.com/videos/390161-How-to-Tie-a-Square-Knot-Properly',
u'file': u'390161.mp4', 'md5': '8b743df908c42f60cf6496586c7f12c3',
u'md5': u'8b743df908c42f60cf6496586c7f12c3', 'info_dict': {
u'info_dict': { 'id': '390161',
u"description": u"The square knot, also known as the reef knot, is one of the oldest, most basic knots to tie, and can be used in many different ways. Here's the proper way to tie a square knot.", 'ext': 'mp4',
u"title": u"How to Tie a Square Knot Properly" 'description': 'The square knot, also known as the reef knot, is one of the oldest, most basic knots to tie, and can be used in many different ways. Here\'s the proper way to tie a square knot.',
'title': 'How to Tie a Square Knot Properly',
} }
} }
@ -24,22 +27,15 @@ class HowcastIE(InfoExtractor):
self.report_extraction(video_id) self.report_extraction(video_id)
video_url = self._search_regex(r'\'?file\'?: "(http://mobile-media\.howcast\.com/[0-9]+\.mp4)', video_url = self._search_regex(r'\'?file\'?: "(http://mobile-media\.howcast\.com/[0-9]+\.mp4)',
webpage, u'video URL') webpage, 'video URL')
video_title = self._html_search_regex(r'<meta content=(?:"([^"]+)"|\'([^\']+)\') property=\'og:title\'',
webpage, u'title')
video_description = self._html_search_regex(r'<meta content=(?:"([^"]+)"|\'([^\']+)\') name=\'description\'', video_description = self._html_search_regex(r'<meta content=(?:"([^"]+)"|\'([^\']+)\') name=\'description\'',
webpage, u'description', fatal=False) webpage, 'description', fatal=False)
thumbnail = self._html_search_regex(r'<meta content=\'(.+?)\' property=\'og:image\'', return {
webpage, u'thumbnail', fatal=False) 'id': video_id,
'url': video_url,
return [{ 'title': self._og_search_title(webpage),
'id': video_id,
'url': video_url,
'ext': 'mp4',
'title': video_title,
'description': video_description, 'description': video_description,
'thumbnail': thumbnail, 'thumbnail': self._og_search_thumbnail(webpage),
}] }

View File

@ -1,35 +1,39 @@
from __future__ import unicode_literals
import re import re
from .common import InfoExtractor from .common import InfoExtractor
class InstagramIE(InfoExtractor): class InstagramIE(InfoExtractor):
_VALID_URL = r'(?:http://)?instagram\.com/p/(.*?)/' _VALID_URL = r'http://instagram\.com/p/(?P<id>.*?)/'
_TEST = { _TEST = {
u'url': u'http://instagram.com/p/aye83DjauH/?foo=bar#abc', 'url': 'http://instagram.com/p/aye83DjauH/?foo=bar#abc',
u'file': u'aye83DjauH.mp4', 'md5': '0d2da106a9d2631273e192b372806516',
u'md5': u'0d2da106a9d2631273e192b372806516', 'info_dict': {
u'info_dict': { 'id': 'aye83DjauH',
u"uploader_id": u"naomipq", 'ext': 'mp4',
u"title": u"Video by naomipq", 'uploader_id': 'naomipq',
u'description': u'md5:1f17f0ab29bd6fe2bfad705f58de3cb8', 'title': 'Video by naomipq',
'description': 'md5:1f17f0ab29bd6fe2bfad705f58de3cb8',
} }
} }
def _real_extract(self, url): def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
video_id = mobj.group(1) video_id = mobj.group('id')
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
uploader_id = self._search_regex(r'"owner":{"username":"(.+?)"', uploader_id = self._search_regex(r'"owner":{"username":"(.+?)"',
webpage, u'uploader id', fatal=False) webpage, 'uploader id', fatal=False)
desc = self._search_regex(r'"caption":"(.*?)"', webpage, u'description', desc = self._search_regex(r'"caption":"(.*?)"', webpage, 'description',
fatal=False) fatal=False)
return [{ return {
'id': video_id, 'id': video_id,
'url': self._og_search_video_url(webpage, secure=False), 'url': self._og_search_video_url(webpage, secure=False),
'ext': 'mp4', 'ext': 'mp4',
'title': u'Video by %s' % uploader_id, 'title': 'Video by %s' % uploader_id,
'thumbnail': self._og_search_thumbnail(webpage), 'thumbnail': self._og_search_thumbnail(webpage),
'uploader_id' : uploader_id, 'uploader_id': uploader_id,
'description': desc, 'description': desc,
}] }

View File

@ -2,7 +2,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import json
import re import re
from .common import InfoExtractor from .common import InfoExtractor

View File

@ -0,0 +1,66 @@
# encoding: utf-8
from __future__ import unicode_literals
import re
from .common import InfoExtractor
class KontrTubeIE(InfoExtractor):
IE_NAME = 'kontrtube'
IE_DESC = 'KontrTube.ru - Труба зовёт'
_VALID_URL = r'http://(?:www\.)?kontrtube\.ru/videos/(?P<id>\d+)/.+'
_TEST = {
'url': 'http://www.kontrtube.ru/videos/2678/nad-olimpiyskoy-derevney-v-sochi-podnyat-rossiyskiy-flag/',
'md5': '975a991a4926c9a85f383a736a2e6b80',
'info_dict': {
'id': '2678',
'ext': 'mp4',
'title': 'Над олимпийской деревней в Сочи поднят российский флаг',
'description': 'md5:80edc4c613d5887ae8ccf1d59432be41',
'thumbnail': 'http://www.kontrtube.ru/contents/videos_screenshots/2000/2678/preview.mp4.jpg',
'duration': 270,
}
}
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
video_id = mobj.group('id')
webpage = self._download_webpage(url, video_id, 'Downloading page')
video_url = self._html_search_regex(r"video_url: '(.+?)/?',", webpage, 'video URL')
thumbnail = self._html_search_regex(r"preview_url: '(.+?)/?',", webpage, 'video thumbnail', fatal=False)
title = self._html_search_regex(r'<title>(.+?) - Труба зовёт - Интересный видеохостинг</title>', webpage,
'video title')
description = self._html_search_meta('description', webpage, 'video description')
mobj = re.search(r'<div class="col_2">Длительность: <span>(?P<minutes>\d+)м:(?P<seconds>\d+)с</span></div>',
webpage)
duration = int(mobj.group('minutes')) * 60 + int(mobj.group('seconds')) if mobj else None
view_count = self._html_search_regex(r'<div class="col_2">Просмотров: <span>(\d+)</span></div>', webpage,
'view count', fatal=False)
view_count = int(view_count) if view_count is not None else None
comment_count = None
comment_str = self._html_search_regex(r'Комментарии: <span>([^<]+)</span>', webpage, 'comment count',
fatal=False)
if comment_str.startswith('комментариев нет'):
comment_count = 0
else:
mobj = re.search(r'\d+ из (?P<total>\d+) комментариев', comment_str)
if mobj:
comment_count = int(mobj.group('total'))
return {
'id': video_id,
'url': video_url,
'thumbnail': thumbnail,
'title': title,
'description': description,
'duration': duration,
'view_count': view_count,
'comment_count': comment_count,
}

View File

@ -4,19 +4,23 @@ from __future__ import unicode_literals
import re import re
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import unified_strdate from ..utils import (
int_or_none,
unified_strdate
)
class LifeNewsIE(InfoExtractor): class LifeNewsIE(InfoExtractor):
IE_NAME = 'lifenews' IE_NAME = 'lifenews'
IE_DESC = 'LIFE | NEWS' IE_DESC = 'LIFE | NEWS'
_VALID_URL = r'http://lifenews\.ru/(?:mobile/)?news/(?P<id>\d+)' _VALID_URL = r'http://lifenews\.ru/(?:mobile/)?news/(?P<id>\d+)'
_TEST = { _TEST = {
'url': 'http://lifenews.ru/news/126342', 'url': 'http://lifenews.ru/news/126342',
'file': '126342.mp4',
'md5': 'e1b50a5c5fb98a6a544250f2e0db570a', 'md5': 'e1b50a5c5fb98a6a544250f2e0db570a',
'info_dict': { 'info_dict': {
'id': '126342',
'ext': 'mp4',
'title': 'МВД разыскивает мужчин, оставивших в IKEA сумку с автоматом', 'title': 'МВД разыскивает мужчин, оставивших в IKEA сумку с автоматом',
'description': 'Камеры наблюдения гипермаркета зафиксировали троих мужчин, спрятавших оружейный арсенал в камере хранения.', 'description': 'Камеры наблюдения гипермаркета зафиксировали троих мужчин, спрятавших оружейный арсенал в камере хранения.',
'thumbnail': 'http://lifenews.ru/static/posts/2014/1/126342/.video.jpg', 'thumbnail': 'http://lifenews.ru/static/posts/2014/1/126342/.video.jpg',
@ -32,7 +36,7 @@ class LifeNewsIE(InfoExtractor):
video_url = self._html_search_regex( video_url = self._html_search_regex(
r'<video.*?src="([^"]+)".*?></video>', webpage, 'video URL') r'<video.*?src="([^"]+)".*?></video>', webpage, 'video URL')
thumbnail = self._html_search_regex( thumbnail = self._html_search_regex(
r'<video.*?poster="([^"]+)".*?"></video>', webpage, 'video thumbnail') r'<video.*?poster="([^"]+)".*?"></video>', webpage, 'video thumbnail')
@ -44,12 +48,14 @@ class LifeNewsIE(InfoExtractor):
description = self._og_search_description(webpage) description = self._og_search_description(webpage)
view_count = self._html_search_regex( view_count = self._html_search_regex(
r'<div class=\'views\'>(\d+)</div>', webpage, 'view count') r'<div class=\'views\'>(\d+)</div>', webpage, 'view count', fatal=False)
comment_count = self._html_search_regex( comment_count = self._html_search_regex(
r'<div class=\'comments\'>(\d+)</div>', webpage, 'comment count') r'<div class=\'comments\'>(\d+)</div>', webpage, 'comment count', fatal=False)
upload_date = self._html_search_regex( upload_date = self._html_search_regex(
r'<time datetime=\'([^\']+)\'>', webpage, 'upload date') r'<time datetime=\'([^\']+)\'>', webpage, 'upload date',fatal=False)
if upload_date is not None:
upload_date = unified_strdate(upload_date)
return { return {
'id': video_id, 'id': video_id,
@ -57,7 +63,7 @@ class LifeNewsIE(InfoExtractor):
'thumbnail': thumbnail, 'thumbnail': thumbnail,
'title': title, 'title': title,
'description': description, 'description': description,
'view_count': view_count, 'view_count': int_or_none(view_count),
'comment_count': comment_count, 'comment_count': int_or_none(comment_count),
'upload_date': unified_strdate(upload_date), 'upload_date': upload_date,
} }

View File

@ -82,12 +82,12 @@ class MTVServicesInfoExtractor(InfoExtractor):
title_el = find_xpath_attr( title_el = find_xpath_attr(
itemdoc, './/{http://search.yahoo.com/mrss/}category', itemdoc, './/{http://search.yahoo.com/mrss/}category',
'scheme', 'urn:mtvn:video_title') 'scheme', 'urn:mtvn:video_title')
if title_el is None:
title_el = itemdoc.find('.//{http://search.yahoo.com/mrss/}title')
if title_el is None: if title_el is None:
title_el = itemdoc.find('.//title') title_el = itemdoc.find('.//title')
if title_el.text is None: if title_el.text is None:
title_el = None title_el = None
if title_el is None:
title_el = itemdoc.find('.//{http://search.yahoo.com/mrss/}title')
title = title_el.text title = title_el.text
if title is None: if title is None:

View File

@ -13,22 +13,22 @@ class NDRIE(InfoExtractor):
_VALID_URL = r'https?://www\.ndr\.de/.+?(?P<id>\d+)\.html' _VALID_URL = r'https?://www\.ndr\.de/.+?(?P<id>\d+)\.html'
_TESTS = [ _TESTS = [
# video
{ {
'url': 'http://www.ndr.de/fernsehen/sendungen/hallo_niedersachsen/media/hallonds19925.html', 'url': 'http://www.ndr.de/fernsehen/sendungen/markt/markt7959.html',
'md5': '20eba151ff165f386643dad9c1da08f7', 'md5': 'e7a6079ca39d3568f4996cb858dd6708',
'note': 'Video file',
'info_dict': { 'info_dict': {
'id': '19925', 'id': '7959',
'ext': 'mp4', 'ext': 'mp4',
'title': 'Hallo Niedersachsen ', 'title': 'Markt - die ganze Sendung',
'description': 'Bei Hallo Niedersachsen um 19:30 Uhr erfahren Sie alles, was am Tag in Niedersachsen los war.', 'description': 'md5:af9179cf07f67c5c12dc6d9997e05725',
'duration': 1722, 'duration': 2655,
}, },
}, },
# audio
{ {
'url': 'http://www.ndr.de/903/audio191719.html', 'url': 'http://www.ndr.de/903/audio191719.html',
'md5': '41ed601768534dd18a9ae34d84798129', 'md5': '41ed601768534dd18a9ae34d84798129',
'note': 'Audio file',
'info_dict': { 'info_dict': {
'id': '191719', 'id': '191719',
'ext': 'mp3', 'ext': 'mp3',

View File

@ -1,3 +1,5 @@
from __future__ import unicode_literals
import re import re
import json import json
@ -12,11 +14,12 @@ class SlideshareIE(InfoExtractor):
_VALID_URL = r'https?://www\.slideshare\.net/[^/]+?/(?P<title>.+?)($|\?)' _VALID_URL = r'https?://www\.slideshare\.net/[^/]+?/(?P<title>.+?)($|\?)'
_TEST = { _TEST = {
u'url': u'http://www.slideshare.net/Dataversity/keynote-presentation-managing-scale-and-complexity', 'url': 'http://www.slideshare.net/Dataversity/keynote-presentation-managing-scale-and-complexity',
u'file': u'25665706.mp4', 'info_dict': {
u'info_dict': { 'id': '25665706',
u'title': u'Managing Scale and Complexity', 'ext': 'mp4',
u'description': u'This was a keynote presentation at the NoSQL Now! 2013 Conference & Expo (http://www.nosqlnow.com). This presentation was given by Adrian Cockcroft from Netflix', 'title': 'Managing Scale and Complexity',
'description': 'This was a keynote presentation at the NoSQL Now! 2013 Conference & Expo (http://www.nosqlnow.com). This presentation was given by Adrian Cockcroft from Netflix.',
}, },
} }
@ -26,15 +29,17 @@ class SlideshareIE(InfoExtractor):
webpage = self._download_webpage(url, page_title) webpage = self._download_webpage(url, page_title)
slideshare_obj = self._search_regex( slideshare_obj = self._search_regex(
r'var slideshare_object = ({.*?}); var user_info =', r'var slideshare_object = ({.*?}); var user_info =',
webpage, u'slideshare object') webpage, 'slideshare object')
info = json.loads(slideshare_obj) info = json.loads(slideshare_obj)
if info['slideshow']['type'] != u'video': if info['slideshow']['type'] != 'video':
raise ExtractorError(u'Webpage type is "%s": only video extraction is supported for Slideshare' % info['slideshow']['type'], expected=True) raise ExtractorError('Webpage type is "%s": only video extraction is supported for Slideshare' % info['slideshow']['type'], expected=True)
doc = info['doc'] doc = info['doc']
bucket = info['jsplayer']['video_bucket'] bucket = info['jsplayer']['video_bucket']
ext = info['jsplayer']['video_extension'] ext = info['jsplayer']['video_extension']
video_url = compat_urlparse.urljoin(bucket, doc + '-SD.' + ext) video_url = compat_urlparse.urljoin(bucket, doc + '-SD.' + ext)
description = self._html_search_regex(
r'<p class="description.*?"[^>]*>(.*?)</p>', webpage, 'description')
return { return {
'_type': 'video', '_type': 'video',
@ -43,5 +48,5 @@ class SlideshareIE(InfoExtractor):
'ext': ext, 'ext': ext,
'url': video_url, 'url': video_url,
'thumbnail': info['slideshow']['pin_image_url'], 'thumbnail': info['slideshow']['pin_image_url'],
'description': self._og_search_description(webpage), 'description': description,
} }

View File

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import re
import json
from .common import InfoExtractor
from ..utils import int_or_none
class StreamCZIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?stream\.cz/.+/(?P<videoid>.+)'
_TEST = {
'url': 'http://www.stream.cz/peklonataliri/765767-ecka-pro-deti',
'md5': '6d3ca61a8d0633c9c542b92fcb936b0c',
'info_dict': {
'id': '765767',
'ext': 'mp4',
'title': 'Peklo na talíři: Éčka pro děti',
'description': 'md5:49ace0df986e95e331d0fe239d421519',
'thumbnail': 'http://im.stream.cz/episode/52961d7e19d423f8f06f0100',
'duration': 256,
},
}
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
video_id = mobj.group('videoid')
webpage = self._download_webpage(url, video_id)
data = self._html_search_regex(r'Stream\.Data\.Episode\((.+?)\);', webpage, 'stream data')
jsonData = json.loads(data)
formats = []
for video in jsonData['instances']:
for video_format in video['instances']:
format_id = video_format['quality']
if format_id == '240p':
quality = 0
elif format_id == '360p':
quality = 1
elif format_id == '480p':
quality = 2
elif format_id == '720p':
quality = 3
formats.append({
'format_id': '%s-%s' % (video_format['type'].split('/')[1], format_id),
'url': video_format['source'],
'quality': quality,
})
self._sort_formats(formats)
return {
'id': str(jsonData['id']),
'title': self._og_search_title(webpage),
'thumbnail': jsonData['episode_image_original_url'].replace('//', 'http://'),
'formats': formats,
'description': self._og_search_description(webpage),
'duration': int_or_none(jsonData['duration']),
'view_count': int_or_none(jsonData['stats_total']),
}

View File

@ -0,0 +1,170 @@
# encoding: utf-8
from __future__ import unicode_literals
import re
from .common import InfoExtractor
from ..utils import (
ExtractorError,
int_or_none
)
class VestiIE(InfoExtractor):
IE_NAME = 'vesti'
IE_DESC = 'Вести.Ru'
_VALID_URL = r'http://(?:.+?\.)?vesti\.ru/(?P<id>.+)'
_TESTS = [
{
'url': 'http://www.vesti.ru/videos?vid=575582&cid=1',
'info_dict': {
'id': '765035',
'ext': 'mp4',
'title': 'Вести.net: биткоины в России не являются законными',
'description': 'md5:d4bb3859dc1177b28a94c5014c35a36b',
'duration': 302,
},
'params': {
# m3u8 download
'skip_download': True,
},
},
{
'url': 'http://www.vesti.ru/only_video.html?vid=576180',
'info_dict': {
'id': '766048',
'ext': 'mp4',
'title': 'США заморозило, Британию затопило',
'description': 'md5:f0ed0695ec05aed27c56a70a58dc4cc1',
'duration': 87,
},
'params': {
# m3u8 download
'skip_download': True,
},
},
{
'url': 'http://sochi2014.vesti.ru/video/index/video_id/766403',
'info_dict': {
'id': '766403',
'ext': 'mp4',
'title': 'XXII зимние Олимпийские игры. Российские хоккеисты стартовали на Олимпиаде с победы',
'description': 'md5:55805dfd35763a890ff50fa9e35e31b3',
'duration': 271,
},
'params': {
# m3u8 download
'skip_download': True,
},
},
{
'url': 'http://sochi2014.vesti.ru/live/play/live_id/301',
'info_dict': {
'id': '51499',
'ext': 'flv',
'title': 'Сочи-2014. Биатлон. Индивидуальная гонка. Мужчины ',
'description': 'md5:9e0ed5c9d2fa1efbfdfed90c9a6d179c',
},
'params': {
# rtmp download
'skip_download': True,
},
'skip': 'Translation has finished'
}
]
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
video_id = mobj.group('id')
page = self._download_webpage(url, video_id, 'Downloading page')
mobj = re.search(r'<meta property="og:video" content=".+?\.swf\?v?id=(?P<id>\d+).*?" />', page)
if mobj:
video_type = 'video'
video_id = mobj.group('id')
else:
mobj = re.search(
r'<div.+?id="current-video-holder".*?>\s*<iframe src="http://player\.rutv\.ru/iframe/(?P<type>[^/]+)/id/(?P<id>\d+)[^"]*"',
page)
if not mobj:
raise ExtractorError('No media found')
video_type = mobj.group('type')
video_id = mobj.group('id')
json_data = self._download_json(
'http://player.rutv.ru/iframe/%splay/id/%s' % ('live-' if video_type == 'live' else '', video_id),
video_id, 'Downloading JSON')
if json_data['errors']:
raise ExtractorError('vesti returned error: %s' % json_data['errors'], expected=True)
playlist = json_data['data']['playlist']
medialist = playlist['medialist']
media = medialist[0]
if media['errors']:
raise ExtractorError('vesti returned error: %s' % media['errors'], expected=True)
view_count = playlist.get('count_views')
priority_transport = playlist['priority_transport']
thumbnail = media['picture']
width = media['width']
height = media['height']
description = media['anons']
title = media['title']
duration = int_or_none(media.get('duration'))
formats = []
for transport, links in media['sources'].items():
for quality, url in links.items():
if transport == 'rtmp':
mobj = re.search(r'^(?P<url>rtmp://[^/]+/(?P<app>.+))/(?P<playpath>.+)$', url)
if not mobj:
continue
fmt = {
'url': mobj.group('url'),
'play_path': mobj.group('playpath'),
'app': mobj.group('app'),
'page_url': 'http://player.rutv.ru',
'player_url': 'http://player.rutv.ru/flash2v/osmf.swf?i=22',
'rtmp_live': True,
'ext': 'flv',
'vbr': int(quality),
}
elif transport == 'm3u8':
fmt = {
'url': url,
'ext': 'mp4',
}
else:
fmt = {
'url': url
}
fmt.update({
'width': width,
'height': height,
'format_id': '%s-%s' % (transport, quality),
'preference': -1 if priority_transport == transport else -2,
})
formats.append(fmt)
if not formats:
raise ExtractorError('No media links available for %s' % video_id)
self._sort_formats(formats)
return {
'id': video_id,
'title': title,
'description': description,
'thumbnail': thumbnail,
'view_count': view_count,
'duration': duration,
'formats': formats,
}

View File

@ -1,3 +1,5 @@
from __future__ import unicode_literals
import os import os
import re import re
@ -10,14 +12,14 @@ from ..utils import (
class XTubeIE(InfoExtractor): class XTubeIE(InfoExtractor):
_VALID_URL = r'^(?:https?://)?(?:www\.)?(?P<url>xtube\.com/watch\.php\?v=(?P<videoid>[^/?&]+))' _VALID_URL = r'^(?:https?://)?(?:www\.)?(?P<url>xtube\.com/watch\.php\?v=(?P<videoid>[^/?&]+))'
_TEST = { _TEST = {
u'url': u'http://www.xtube.com/watch.php?v=kVTUy_G222_', 'url': 'http://www.xtube.com/watch.php?v=kVTUy_G222_',
u'file': u'kVTUy_G222_.mp4', 'file': 'kVTUy_G222_.mp4',
u'md5': u'092fbdd3cbe292c920ef6fc6a8a9cdab', 'md5': '092fbdd3cbe292c920ef6fc6a8a9cdab',
u'info_dict': { 'info_dict': {
u"title": u"strange erotica", "title": "strange erotica",
u"description": u"surreal gay themed erotica...almost an ET kind of thing", "description": "surreal gay themed erotica...almost an ET kind of thing",
u"uploader": u"greenshowers", "uploader": "greenshowers",
u"age_limit": 18, "age_limit": 18,
} }
} }
@ -30,10 +32,10 @@ class XTubeIE(InfoExtractor):
req.add_header('Cookie', 'age_verified=1') req.add_header('Cookie', 'age_verified=1')
webpage = self._download_webpage(req, video_id) webpage = self._download_webpage(req, video_id)
video_title = self._html_search_regex(r'<div class="p_5px[^>]*>([^<]+)', webpage, u'title') video_title = self._html_search_regex(r'<div class="p_5px[^>]*>([^<]+)', webpage, 'title')
video_uploader = self._html_search_regex(r'so_s\.addVariable\("owner_u", "([^"]+)', webpage, u'uploader', fatal=False) video_uploader = self._html_search_regex(r'so_s\.addVariable\("owner_u", "([^"]+)', webpage, 'uploader', fatal=False)
video_description = self._html_search_regex(r'<p class="video_description">([^<]+)', webpage, u'description', fatal=False) video_description = self._html_search_regex(r'<p class="video_description">([^<]+)', webpage, 'description', fatal=False)
video_url= self._html_search_regex(r'var videoMp4 = "([^"]+)', webpage, u'video_url').replace('\\/', '/') video_url= self._html_search_regex(r'var videoMp4 = "([^"]+)', webpage, 'video_url').replace('\\/', '/')
path = compat_urllib_parse_urlparse(video_url).path path = compat_urllib_parse_urlparse(video_url).path
extension = os.path.splitext(path)[1][1:] extension = os.path.splitext(path)[1][1:]
format = path.split('/')[5].split('_')[:2] format = path.split('/')[5].split('_')[:2]

View File

@ -1,3 +1,5 @@
from __future__ import unicode_literals
import itertools import itertools
import json import json
import re import re
@ -12,25 +14,25 @@ from ..utils import (
class YahooIE(InfoExtractor): class YahooIE(InfoExtractor):
IE_DESC = u'Yahoo screen' IE_DESC = 'Yahoo screen'
_VALID_URL = r'http://screen\.yahoo\.com/.*?-(?P<id>\d*?)\.html' _VALID_URL = r'http://screen\.yahoo\.com/.*?-(?P<id>\d*?)\.html'
_TESTS = [ _TESTS = [
{ {
u'url': u'http://screen.yahoo.com/julian-smith-travis-legg-watch-214727115.html', 'url': 'http://screen.yahoo.com/julian-smith-travis-legg-watch-214727115.html',
u'file': u'214727115.mp4', 'file': '214727115.mp4',
u'md5': u'4962b075c08be8690a922ee026d05e69', 'md5': '4962b075c08be8690a922ee026d05e69',
u'info_dict': { 'info_dict': {
u'title': u'Julian Smith & Travis Legg Watch Julian Smith', 'title': 'Julian Smith & Travis Legg Watch Julian Smith',
u'description': u'Julian and Travis watch Julian Smith', 'description': 'Julian and Travis watch Julian Smith',
}, },
}, },
{ {
u'url': u'http://screen.yahoo.com/wired/codefellas-s1-ep12-cougar-lies-103000935.html', 'url': 'http://screen.yahoo.com/wired/codefellas-s1-ep12-cougar-lies-103000935.html',
u'file': u'103000935.mp4', 'file': '103000935.mp4',
u'md5': u'd6e6fc6e1313c608f316ddad7b82b306', 'md5': 'd6e6fc6e1313c608f316ddad7b82b306',
u'info_dict': { 'info_dict': {
u'title': u'Codefellas - The Cougar Lies with Spanish Moss', 'title': 'Codefellas - The Cougar Lies with Spanish Moss',
u'description': u'Agent Topple\'s mustache does its dirty work, and Nicole brokers a deal for peace. But why is the NSA collecting millions of Instagram brunch photos? And if your waffles have nothing to hide, what are they so worried about?', 'description': 'Agent Topple\'s mustache does its dirty work, and Nicole brokers a deal for peace. But why is the NSA collecting millions of Instagram brunch photos? And if your waffles have nothing to hide, what are they so worried about?',
}, },
}, },
] ]
@ -41,7 +43,7 @@ class YahooIE(InfoExtractor):
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
items_json = self._search_regex(r'mediaItems: ({.*?})$', items_json = self._search_regex(r'mediaItems: ({.*?})$',
webpage, u'items', flags=re.MULTILINE) webpage, 'items', flags=re.MULTILINE)
items = json.loads(items_json) items = json.loads(items_json)
info = items['mediaItems']['query']['results']['mediaObj'][0] info = items['mediaItems']['query']['results']['mediaObj'][0]
# The 'meta' field is not always in the video webpage, we request it # The 'meta' field is not always in the video webpage, we request it
@ -60,7 +62,7 @@ class YahooIE(InfoExtractor):
}) })
query_result_json = self._download_webpage( query_result_json = self._download_webpage(
'http://video.query.yahoo.com/v1/public/yql?' + data, 'http://video.query.yahoo.com/v1/public/yql?' + data,
video_id, u'Downloading video info') video_id, 'Downloading video info')
query_result = json.loads(query_result_json) query_result = json.loads(query_result_json)
info = query_result['query']['results']['mediaObj'][0] info = query_result['query']['results']['mediaObj'][0]
meta = info['meta'] meta = info['meta']
@ -103,13 +105,13 @@ class YahooNewsIE(YahooIE):
_VALID_URL = r'http://news\.yahoo\.com/video/.*?-(?P<id>\d*?)\.html' _VALID_URL = r'http://news\.yahoo\.com/video/.*?-(?P<id>\d*?)\.html'
_TEST = { _TEST = {
u'url': u'http://news.yahoo.com/video/china-moses-crazy-blues-104538833.html', 'url': 'http://news.yahoo.com/video/china-moses-crazy-blues-104538833.html',
u'md5': u'67010fdf3a08d290e060a4dd96baa07b', 'md5': '67010fdf3a08d290e060a4dd96baa07b',
u'info_dict': { 'info_dict': {
u'id': u'104538833', 'id': '104538833',
u'ext': u'mp4', 'ext': 'mp4',
u'title': u'China Moses Is Crazy About the Blues', 'title': 'China Moses Is Crazy About the Blues',
u'description': u'md5:9900ab8cd5808175c7b3fe55b979bed0', 'description': 'md5:9900ab8cd5808175c7b3fe55b979bed0',
}, },
} }
@ -120,14 +122,14 @@ class YahooNewsIE(YahooIE):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
video_id = mobj.group('id') video_id = mobj.group('id')
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
long_id = self._search_regex(r'contentId: \'(.+?)\',', webpage, u'long id') long_id = self._search_regex(r'contentId: \'(.+?)\',', webpage, 'long id')
return self._get_info(long_id, video_id) return self._get_info(long_id, video_id)
class YahooSearchIE(SearchInfoExtractor): class YahooSearchIE(SearchInfoExtractor):
IE_DESC = u'Yahoo screen search' IE_DESC = 'Yahoo screen search'
_MAX_RESULTS = 1000 _MAX_RESULTS = 1000
IE_NAME = u'screen.yahoo:search' IE_NAME = 'screen.yahoo:search'
_SEARCH_KEY = 'yvsearch' _SEARCH_KEY = 'yvsearch'
def _get_n_results(self, query, n): def _get_n_results(self, query, n):
@ -139,12 +141,12 @@ class YahooSearchIE(SearchInfoExtractor):
'entries': [] 'entries': []
} }
for pagenum in itertools.count(0): for pagenum in itertools.count(0):
result_url = u'http://video.search.yahoo.com/search/?p=%s&fr=screen&o=js&gs=0&b=%d' % (compat_urllib_parse.quote_plus(query), pagenum * 30) result_url = 'http://video.search.yahoo.com/search/?p=%s&fr=screen&o=js&gs=0&b=%d' % (compat_urllib_parse.quote_plus(query), pagenum * 30)
webpage = self._download_webpage(result_url, query, webpage = self._download_webpage(result_url, query,
note='Downloading results page '+str(pagenum+1)) note='Downloading results page '+str(pagenum+1))
info = json.loads(webpage) info = json.loads(webpage)
m = info[u'm'] m = info['m']
results = info[u'results'] results = info['results']
for (i, r) in enumerate(results): for (i, r) in enumerate(results):
if (pagenum * 30) +i >= n: if (pagenum * 30) +i >= n:
@ -152,7 +154,7 @@ class YahooSearchIE(SearchInfoExtractor):
mobj = re.search(r'(?P<url>screen\.yahoo\.com/.*?-\d*?\.html)"', r) mobj = re.search(r'(?P<url>screen\.yahoo\.com/.*?-\d*?\.html)"', r)
e = self.url_result('http://' + mobj.group('url'), 'Yahoo') e = self.url_result('http://' + mobj.group('url'), 'Yahoo')
res['entries'].append(e) res['entries'].append(e)
if (pagenum * 30 +i >= n) or (m[u'last'] >= (m[u'total'] -1)): if (pagenum * 30 +i >= n) or (m['last'] >= (m['total'] -1)):
break break
return res return res

View File

@ -34,6 +34,7 @@ from ..utils import (
unified_strdate, unified_strdate,
orderedSet, orderedSet,
write_json_file, write_json_file,
uppercase_escape,
) )
class YoutubeBaseInfoExtractor(InfoExtractor): class YoutubeBaseInfoExtractor(InfoExtractor):
@ -136,7 +137,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
(?:https?://|//)? # http(s):// or protocol-independent URL (optional) (?:https?://|//)? # http(s):// or protocol-independent URL (optional)
(?:(?:(?:(?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie)?\.com/| (?:(?:(?:(?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie)?\.com/|
(?:www\.)?deturl\.com/www\.youtube\.com/| (?:www\.)?deturl\.com/www\.youtube\.com/|
(?:www\.)?pwnyoutube\.com| (?:www\.)?pwnyoutube\.com/|
tube\.majestyc\.net/| tube\.majestyc\.net/|
youtube\.googleapis\.com/) # the various hostnames, with wildcard subdomains youtube\.googleapis\.com/) # the various hostnames, with wildcard subdomains
(?:.*?\#/)? # handle anchor (#/) redirect urls (?:.*?\#/)? # handle anchor (#/) redirect urls
@ -1590,11 +1591,10 @@ class YoutubeChannelIE(InfoExtractor):
# Download all channel pages using the json-based channel_ajax query # Download all channel pages using the json-based channel_ajax query
for pagenum in itertools.count(1): for pagenum in itertools.count(1):
url = self._MORE_PAGES_URL % (pagenum, channel_id) url = self._MORE_PAGES_URL % (pagenum, channel_id)
page = self._download_webpage(url, channel_id, page = self._download_json(
u'Downloading page #%s' % pagenum) url, channel_id, note=u'Downloading page #%s' % pagenum,
transform_source=uppercase_escape)
page = json.loads(page)
ids_in_page = self.extract_videos_from_page(page['content_html']) ids_in_page = self.extract_videos_from_page(page['content_html'])
video_ids.extend(ids_in_page) video_ids.extend(ids_in_page)

View File

@ -756,9 +756,9 @@ def unified_strdate(date_str):
"""Return a string with the date in the format YYYYMMDD""" """Return a string with the date in the format YYYYMMDD"""
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:]*$', '', date_str) date_str = re.sub(r' ?(\+|-)[0-9]{2}:?[0-9]{2}$', '', date_str)
format_expressions = [ format_expressions = [
'%d %B %Y', '%d %B %Y',
'%B %d %Y', '%B %d %Y',
@ -1214,3 +1214,9 @@ class PagedList(object):
if end == nextfirstid: if end == nextfirstid:
break break
return res return res
def uppercase_escape(s):
return re.sub(
r'\\U([0-9a-fA-F]{8})',
lambda m: compat_chr(int(m.group(1), base=16)), s)

View File

@ -1,2 +1,2 @@
__version__ = '2014.02.08.2' __version__ = '2014.02.13'