mirror of
https://source.netsyms.com/Mirrors/youtube-dl
synced 2026-03-30 22:25:45 +00:00
Compare commits
35 Commits
2014.09.24
...
2014.09.28
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88fbe4c2cc | ||
|
|
394599f422 | ||
|
|
ed9266db90 | ||
|
|
f4b1c7adb8 | ||
|
|
c95eeb7b80 | ||
|
|
5e43e3803c | ||
|
|
a89435a7a8 | ||
|
|
a0a90b3ba1 | ||
|
|
c664182323 | ||
|
|
6be1cd4ddb | ||
|
|
ee0d90707a | ||
|
|
f776d8f608 | ||
|
|
b3ac3a51ac | ||
|
|
0b75c2a88b | ||
|
|
68b0973046 | ||
|
|
3a203b8bfa | ||
|
|
b66745288e | ||
|
|
2a1325fdde | ||
|
|
2f9e8776df | ||
|
|
497339fa0e | ||
|
|
8e6f8051f0 | ||
|
|
11b3ce8509 | ||
|
|
9a0d98bb40 | ||
|
|
fbd3162e49 | ||
|
|
54e9a4af95 | ||
|
|
8a32b82e46 | ||
|
|
fec02bcc90 | ||
|
|
c6e90caaa6 | ||
|
|
4bbf157794 | ||
|
|
6b08cdf626 | ||
|
|
b686fc18da | ||
|
|
0b97f3a936 | ||
|
|
eb73f2649f | ||
|
|
f0b5d6af74 | ||
|
|
746c67d72f |
@@ -1250,12 +1250,13 @@ class YoutubeDL(object):
|
||||
# urllib chokes on URLs with non-ASCII characters (see http://bugs.python.org/issue3991)
|
||||
# To work around aforementioned issue we will replace request's original URL with
|
||||
# percent-encoded one
|
||||
url = req if isinstance(req, compat_str) else req.get_full_url()
|
||||
req_is_string = isinstance(req, basestring if sys.version_info < (3, 0) else compat_str)
|
||||
url = req if req_is_string else req.get_full_url()
|
||||
url_escaped = escape_url(url)
|
||||
|
||||
# Substitute URL if any change after escaping
|
||||
if url != url_escaped:
|
||||
if isinstance(req, compat_str):
|
||||
if req_is_string:
|
||||
req = url_escaped
|
||||
else:
|
||||
req = compat_urllib_request.Request(
|
||||
|
||||
@@ -78,6 +78,7 @@ __authors__ = (
|
||||
'Hari Padmanaban',
|
||||
'Carlos Ramos',
|
||||
'5moufl',
|
||||
'lenaten',
|
||||
)
|
||||
|
||||
__license__ = 'Public Domain'
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from .common import FileDownloader
|
||||
from .hls import HlsFD
|
||||
from .hls import NativeHlsFD
|
||||
from .http import HttpFD
|
||||
from .mplayer import MplayerFD
|
||||
from .rtmp import RtmpFD
|
||||
@@ -19,6 +20,8 @@ def get_suitable_downloader(info_dict):
|
||||
|
||||
if url.startswith('rtmp'):
|
||||
return RtmpFD
|
||||
if protocol == 'm3u8_native':
|
||||
return NativeHlsFD
|
||||
if (protocol == 'm3u8') or (protocol is None and determine_ext(url) == 'm3u8'):
|
||||
return HlsFD
|
||||
if url.startswith('mms') or url.startswith('rtsp'):
|
||||
|
||||
@@ -42,6 +42,7 @@ class FileDownloader(object):
|
||||
Subclasses of this one must re-define the real_download method.
|
||||
"""
|
||||
|
||||
_TEST_FILE_SIZE = 10241
|
||||
params = None
|
||||
|
||||
def __init__(self, ydl, params):
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from .common import FileDownloader
|
||||
from ..utils import (
|
||||
compat_urlparse,
|
||||
compat_urllib_request,
|
||||
check_executable,
|
||||
encodeFilename,
|
||||
)
|
||||
@@ -43,3 +48,57 @@ class HlsFD(FileDownloader):
|
||||
self.to_stderr(u"\n")
|
||||
self.report_error(u'%s exited with code %d' % (program, retval))
|
||||
return False
|
||||
|
||||
|
||||
class NativeHlsFD(FileDownloader):
|
||||
""" A more limited implementation that does not require ffmpeg """
|
||||
|
||||
def real_download(self, filename, info_dict):
|
||||
url = info_dict['url']
|
||||
self.report_destination(filename)
|
||||
tmpfilename = self.temp_name(filename)
|
||||
|
||||
self.to_screen(
|
||||
'[hlsnative] %s: Downloading m3u8 manifest' % info_dict['id'])
|
||||
data = self.ydl.urlopen(url).read()
|
||||
s = data.decode('utf-8', 'ignore')
|
||||
segment_urls = []
|
||||
for line in s.splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
segment_url = (
|
||||
line
|
||||
if re.match(r'^https?://', line)
|
||||
else compat_urlparse.urljoin(url, line))
|
||||
segment_urls.append(segment_url)
|
||||
|
||||
is_test = self.params.get('test', False)
|
||||
remaining_bytes = self._TEST_FILE_SIZE if is_test else None
|
||||
byte_counter = 0
|
||||
with open(tmpfilename, 'wb') as outf:
|
||||
for i, segurl in enumerate(segment_urls):
|
||||
self.to_screen(
|
||||
'[hlsnative] %s: Downloading segment %d / %d' %
|
||||
(info_dict['id'], i + 1, len(segment_urls)))
|
||||
seg_req = compat_urllib_request.Request(segurl)
|
||||
if remaining_bytes is not None:
|
||||
seg_req.add_header('Range', 'bytes=0-%d' % (remaining_bytes - 1))
|
||||
|
||||
segment = self.ydl.urlopen(seg_req).read()
|
||||
if remaining_bytes is not None:
|
||||
segment = segment[:remaining_bytes]
|
||||
remaining_bytes -= len(segment)
|
||||
outf.write(segment)
|
||||
byte_counter += len(segment)
|
||||
if remaining_bytes is not None and remaining_bytes <= 0:
|
||||
break
|
||||
|
||||
self._hook_progress({
|
||||
'downloaded_bytes': byte_counter,
|
||||
'total_bytes': byte_counter,
|
||||
'filename': filename,
|
||||
'status': 'finished',
|
||||
})
|
||||
self.try_rename(tmpfilename, filename)
|
||||
return True
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ from ..utils import (
|
||||
|
||||
|
||||
class HttpFD(FileDownloader):
|
||||
_TEST_FILE_SIZE = 10241
|
||||
|
||||
def real_download(self, filename, info_dict):
|
||||
url = info_dict['url']
|
||||
tmpfilename = self.temp_name(filename)
|
||||
|
||||
@@ -261,6 +261,7 @@ from .nrk import (
|
||||
from .ntv import NTVIE
|
||||
from .nytimes import NYTimesIE
|
||||
from .nuvid import NuvidIE
|
||||
from .oktoberfesttv import OktoberfestTVIE
|
||||
from .ooyala import OoyalaIE
|
||||
from .orf import (
|
||||
ORFTVthekIE,
|
||||
@@ -339,6 +340,7 @@ from .spankwire import SpankwireIE
|
||||
from .spiegel import SpiegelIE, SpiegelArticleIE
|
||||
from .spiegeltv import SpiegeltvIE
|
||||
from .spike import SpikeIE
|
||||
from .sport5 import Sport5IE
|
||||
from .sportdeutschland import SportDeutschlandIE
|
||||
from .stanfordoc import StanfordOpenClassroomIE
|
||||
from .steam import SteamIE
|
||||
@@ -450,6 +452,7 @@ from .yahoo import (
|
||||
YahooNewsIE,
|
||||
YahooSearchIE,
|
||||
)
|
||||
from .ynet import YnetIE
|
||||
from .youjizz import YouJizzIE
|
||||
from .youku import YoukuIE
|
||||
from .youporn import YouPornIE
|
||||
|
||||
@@ -22,8 +22,7 @@ class ABCIE(InfoExtractor):
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
video_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
urls_info_json = self._search_regex(
|
||||
|
||||
@@ -35,7 +35,7 @@ class AnySexIE(InfoExtractor):
|
||||
|
||||
title = self._html_search_regex(r'<title>(.*?)</title>', webpage, 'title')
|
||||
description = self._html_search_regex(
|
||||
r'<div class="description">([^<]+)</div>', webpage, 'description', fatal=False)
|
||||
r'<div class="description"[^>]*>([^<]+)</div>', webpage, 'description', fatal=False)
|
||||
thumbnail = self._html_search_regex(
|
||||
r'preview_url\s*:\s*\'(.*?)\'', webpage, 'thumbnail', fatal=False)
|
||||
|
||||
@@ -43,7 +43,7 @@ class AnySexIE(InfoExtractor):
|
||||
r'<a href="http://anysex\.com/categories/[^"]+" title="[^"]*">([^<]+)</a>', webpage)
|
||||
|
||||
duration = parse_duration(self._search_regex(
|
||||
r'<b>Duration:</b> (\d+:\d+)', webpage, 'duration', fatal=False))
|
||||
r'<b>Duration:</b> (?:<q itemprop="duration">)?(\d+:\d+)', webpage, 'duration', fatal=False))
|
||||
view_count = int_or_none(self._html_search_regex(
|
||||
r'<b>Views:</b> (\d+)', webpage, 'view count', fatal=False))
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import netrc
|
||||
@@ -15,6 +16,7 @@ from ..utils import (
|
||||
compat_http_client,
|
||||
compat_urllib_error,
|
||||
compat_urllib_parse_urlparse,
|
||||
compat_urlparse,
|
||||
compat_str,
|
||||
|
||||
clean_html,
|
||||
@@ -163,6 +165,14 @@ class InfoExtractor(object):
|
||||
cls._VALID_URL_RE = re.compile(cls._VALID_URL)
|
||||
return cls._VALID_URL_RE.match(url) is not None
|
||||
|
||||
@classmethod
|
||||
def _match_id(cls, url):
|
||||
if '_VALID_URL_RE' not in cls.__dict__:
|
||||
cls._VALID_URL_RE = re.compile(cls._VALID_URL)
|
||||
m = cls._VALID_URL_RE.match(url)
|
||||
assert m
|
||||
return m.group('id')
|
||||
|
||||
@classmethod
|
||||
def working(cls):
|
||||
"""Getter method for _WORKING."""
|
||||
@@ -640,7 +650,9 @@ class InfoExtractor(object):
|
||||
|
||||
return formats
|
||||
|
||||
def _extract_m3u8_formats(self, m3u8_url, video_id, ext=None):
|
||||
def _extract_m3u8_formats(self, m3u8_url, video_id, ext=None,
|
||||
entry_protocol='m3u8', preference=None):
|
||||
|
||||
formats = [{
|
||||
'format_id': 'm3u8-meta',
|
||||
'url': m3u8_url,
|
||||
@@ -651,6 +663,11 @@ class InfoExtractor(object):
|
||||
'format_note': 'Quality selection URL',
|
||||
}]
|
||||
|
||||
format_url = lambda u: (
|
||||
u
|
||||
if re.match(r'^https?://', u)
|
||||
else compat_urlparse.urljoin(m3u8_url, u))
|
||||
|
||||
m3u8_doc = self._download_webpage(m3u8_url, video_id)
|
||||
last_info = None
|
||||
kv_rex = re.compile(
|
||||
@@ -667,15 +684,17 @@ class InfoExtractor(object):
|
||||
continue
|
||||
else:
|
||||
if last_info is None:
|
||||
formats.append({'url': line})
|
||||
formats.append({'url': format_url(line)})
|
||||
continue
|
||||
tbr = int_or_none(last_info.get('BANDWIDTH'), scale=1000)
|
||||
|
||||
f = {
|
||||
'format_id': 'm3u8-%d' % (tbr if tbr else len(formats)),
|
||||
'url': line.strip(),
|
||||
'url': format_url(line.strip()),
|
||||
'tbr': tbr,
|
||||
'ext': ext,
|
||||
'protocol': entry_protocol,
|
||||
'preference': preference,
|
||||
}
|
||||
codecs = last_info.get('CODECS')
|
||||
if codecs:
|
||||
@@ -695,6 +714,12 @@ class InfoExtractor(object):
|
||||
self._sort_formats(formats)
|
||||
return formats
|
||||
|
||||
def _live_title(self, name):
|
||||
""" Generate the title for a live video """
|
||||
now = datetime.datetime.now()
|
||||
now_str = now.strftime("%Y-%m-%d %H:%M")
|
||||
return name + ' ' + now_str
|
||||
|
||||
|
||||
class SearchInfoExtractor(InfoExtractor):
|
||||
"""
|
||||
|
||||
@@ -9,7 +9,7 @@ import xml.etree.ElementTree
|
||||
|
||||
from hashlib import sha1
|
||||
from math import pow, sqrt, floor
|
||||
from .common import InfoExtractor
|
||||
from .subtitles import SubtitlesInfoExtractor
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
compat_urllib_parse,
|
||||
@@ -26,7 +26,7 @@ from ..aes import (
|
||||
)
|
||||
|
||||
|
||||
class CrunchyrollIE(InfoExtractor):
|
||||
class CrunchyrollIE(SubtitlesInfoExtractor):
|
||||
_VALID_URL = r'https?://(?:(?P<prefix>www|m)\.)?(?P<url>crunchyroll\.com/(?:[^/]*/[^/?&]*?|media/\?id=)(?P<video_id>[0-9]+))(?:[/?&]|$)'
|
||||
_TEST = {
|
||||
'url': 'http://www.crunchyroll.com/wanna-be-the-strongest-in-the-world/episode-1-an-idol-wrestler-is-born-645513',
|
||||
@@ -271,6 +271,10 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
else:
|
||||
subtitles[lang_code] = self._convert_subtitles_to_srt(subtitle)
|
||||
|
||||
if self._downloader.params.get('listsubtitles', False):
|
||||
self._list_available_subtitles(video_id, subtitles)
|
||||
return
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': video_title,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# encoding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
@@ -7,20 +9,20 @@ from ..utils import ExtractorError
|
||||
|
||||
|
||||
class EitbIE(InfoExtractor):
|
||||
IE_NAME = u'eitb.tv'
|
||||
IE_NAME = 'eitb.tv'
|
||||
_VALID_URL = r'https?://www\.eitb\.tv/(eu/bideoa|es/video)/[^/]+/(?P<playlist_id>\d+)/(?P<chapter_id>\d+)'
|
||||
|
||||
_TEST = {
|
||||
u'add_ie': ['Brightcove'],
|
||||
u'url': u'http://www.eitb.tv/es/video/60-minutos-60-minutos-2013-2014/2677100210001/2743577154001/lasa-y-zabala-30-anos/',
|
||||
u'md5': u'edf4436247185adee3ea18ce64c47998',
|
||||
u'info_dict': {
|
||||
u'id': u'2743577154001',
|
||||
u'ext': u'mp4',
|
||||
u'title': u'60 minutos (Lasa y Zabala, 30 años)',
|
||||
'add_ie': ['Brightcove'],
|
||||
'url': 'http://www.eitb.tv/es/video/60-minutos-60-minutos-2013-2014/2677100210001/2743577154001/lasa-y-zabala-30-anos/',
|
||||
'md5': 'edf4436247185adee3ea18ce64c47998',
|
||||
'info_dict': {
|
||||
'id': '2743577154001',
|
||||
'ext': 'mp4',
|
||||
'title': '60 minutos (Lasa y Zabala, 30 años)',
|
||||
# All videos from eitb has this description in the brightcove info
|
||||
u'description': u'.',
|
||||
u'uploader': u'Euskal Telebista',
|
||||
'description': '.',
|
||||
'uploader': 'Euskal Telebista',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -30,7 +32,7 @@ class EitbIE(InfoExtractor):
|
||||
webpage = self._download_webpage(url, chapter_id)
|
||||
bc_url = BrightcoveIE._extract_brightcove_url(webpage)
|
||||
if bc_url is None:
|
||||
raise ExtractorError(u'Could not extract the Brightcove url')
|
||||
raise ExtractorError('Could not extract the Brightcove url')
|
||||
# The BrightcoveExperience object doesn't contain the video id, we set
|
||||
# it manually
|
||||
bc_url += '&%40videoPlayer={0}'.format(chapter_id)
|
||||
|
||||
@@ -7,6 +7,7 @@ from ..utils import (
|
||||
compat_urllib_parse_urlparse,
|
||||
compat_urllib_request,
|
||||
compat_urllib_parse,
|
||||
str_to_int,
|
||||
)
|
||||
|
||||
|
||||
@@ -20,6 +21,7 @@ class ExtremeTubeIE(InfoExtractor):
|
||||
'ext': 'mp4',
|
||||
'title': 'Music Video 14 british euro brit european cumshots swallow',
|
||||
'uploader': 'unknown',
|
||||
'view_count': int,
|
||||
'age_limit': 18,
|
||||
}
|
||||
}, {
|
||||
@@ -39,8 +41,12 @@ class ExtremeTubeIE(InfoExtractor):
|
||||
video_title = self._html_search_regex(
|
||||
r'<h1 [^>]*?title="([^"]+)"[^>]*>', webpage, 'title')
|
||||
uploader = self._html_search_regex(
|
||||
r'>Posted by:(?=<)(?:\s|<[^>]*>)*(.+?)\|', webpage, 'uploader',
|
||||
fatal=False)
|
||||
r'Uploaded by:\s*</strong>\s*(.+?)\s*</div>',
|
||||
webpage, 'uploader', fatal=False)
|
||||
view_count = str_to_int(self._html_search_regex(
|
||||
r'Views:\s*</strong>\s*<span>([\d,\.]+)</span>',
|
||||
webpage, 'view count', fatal=False))
|
||||
|
||||
video_url = compat_urllib_parse.unquote(self._html_search_regex(
|
||||
r'video_url=(.+?)&', webpage, 'video_url'))
|
||||
path = compat_urllib_parse_urlparse(video_url).path
|
||||
@@ -51,6 +57,7 @@ class ExtremeTubeIE(InfoExtractor):
|
||||
'id': video_id,
|
||||
'title': video_title,
|
||||
'uploader': uploader,
|
||||
'view_count': view_count,
|
||||
'url': video_url,
|
||||
'format': format,
|
||||
'format_id': format,
|
||||
|
||||
@@ -382,6 +382,19 @@ class GenericIE(InfoExtractor):
|
||||
'thumbnail': 're:^https?://.*\.jpg$',
|
||||
},
|
||||
},
|
||||
# Wistia embed
|
||||
{
|
||||
'url': 'http://education-portal.com/academy/lesson/north-american-exploration-failed-colonies-of-spain-france-england.html#lesson',
|
||||
'md5': '8788b683c777a5cf25621eaf286d0c23',
|
||||
'info_dict': {
|
||||
'id': '1cfaf6b7ea',
|
||||
'ext': 'mov',
|
||||
'title': 'md5:51364a8d3d009997ba99656004b5e20d',
|
||||
'duration': 643.0,
|
||||
'filesize': 182808282,
|
||||
'uploader': 'education-portal.com',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
def report_download_webpage(self, video_id):
|
||||
@@ -631,7 +644,7 @@ class GenericIE(InfoExtractor):
|
||||
)
|
||||
(["\'])
|
||||
(?P<url>(?:https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/
|
||||
(?:embed|v)/.+?)
|
||||
(?:embed|v|p)/.+?)
|
||||
\1''', webpage)
|
||||
if matches:
|
||||
return _playlist_from_matches(
|
||||
@@ -656,6 +669,16 @@ class GenericIE(InfoExtractor):
|
||||
'title': video_title,
|
||||
'id': video_id,
|
||||
}
|
||||
match = re.search(r'(?:id=["\']wistia_|data-wistiaid=["\']|Wistia\.embed\(["\'])(?P<id>[^"\']+)', webpage)
|
||||
if match:
|
||||
return {
|
||||
'_type': 'url_transparent',
|
||||
'url': 'http://fast.wistia.net/embed/iframe/{0:}'.format(match.group('id')),
|
||||
'ie_key': 'Wistia',
|
||||
'uploader': video_uploader,
|
||||
'title': video_title,
|
||||
'id': match.group('id')
|
||||
}
|
||||
|
||||
# Look for embedded blip.tv player
|
||||
mobj = re.search(r'<meta\s[^>]*https?://api\.blip\.tv/\w+/redirect/\w+/(\d+)', webpage)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from .common import InfoExtractor
|
||||
@@ -33,9 +32,7 @@ class MuenchenTVIE(InfoExtractor):
|
||||
display_id = 'live'
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
|
||||
now = datetime.datetime.now()
|
||||
now_str = now.strftime("%Y-%m-%d %H:%M")
|
||||
title = self._og_search_title(webpage) + ' ' + now_str
|
||||
title = self._live_title(self._og_search_title(webpage))
|
||||
|
||||
data_js = self._search_regex(
|
||||
r'(?s)\nplaylist:\s*(\[.*?}\]),related:',
|
||||
|
||||
47
youtube_dl/extractor/oktoberfesttv.py
Normal file
47
youtube_dl/extractor/oktoberfesttv.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# encoding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .common import InfoExtractor
|
||||
|
||||
|
||||
class OktoberfestTVIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://www\.oktoberfest-tv\.de/[^/]+/[^/]+/video/(?P<id>[^/?#]+)'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://www.oktoberfest-tv.de/de/kameras/video/hb-zelt',
|
||||
'info_dict': {
|
||||
'id': 'hb-zelt',
|
||||
'ext': 'mp4',
|
||||
'title': 're:^Live-Kamera: Hofbräuzelt [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
||||
'thumbnail': 're:^https?://.*\.jpg$',
|
||||
'is_live': True,
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
title = self._live_title(self._html_search_regex(
|
||||
r'<h1><strong>.*?</strong>(.*?)</h1>', webpage, 'title'))
|
||||
|
||||
clip = self._search_regex(
|
||||
r"clip:\s*\{\s*url:\s*'([^']+)'", webpage, 'clip')
|
||||
ncurl = self._search_regex(
|
||||
r"netConnectionUrl:\s*'([^']+)'", webpage, 'rtmp base')
|
||||
video_url = ncurl + clip
|
||||
thumbnail = self._search_regex(
|
||||
r"canvas:\s*\{\s*backgroundImage:\s*'url\(([^)]+)\)'", webpage,
|
||||
'thumbnail', fatal=False)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'url': video_url,
|
||||
'ext': 'mp4',
|
||||
'is_live': True,
|
||||
'thumbnail': thumbnail,
|
||||
}
|
||||
92
youtube_dl/extractor/sport5.py
Normal file
92
youtube_dl/extractor/sport5.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import ExtractorError
|
||||
|
||||
|
||||
class Sport5IE(InfoExtractor):
|
||||
_VALID_URL = r'http://(?:www|vod)?\.sport5\.co\.il/.*\b(?:Vi|docID)=(?P<id>\d+)'
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://vod.sport5.co.il/?Vc=147&Vi=176331&Page=1',
|
||||
'info_dict': {
|
||||
'id': 's5-Y59xx1-GUh2',
|
||||
'ext': 'mp4',
|
||||
'title': 'ולנסיה-קורדובה 0:3',
|
||||
'description': 'אלקאסר, גאייה ופגולי סידרו לקבוצה של נונו ניצחון על קורדובה ואת המקום הראשון בליגה',
|
||||
'duration': 228,
|
||||
'categories': list,
|
||||
},
|
||||
'skip': 'Blocked outside of Israel',
|
||||
}, {
|
||||
'url': 'http://www.sport5.co.il/articles.aspx?FolderID=3075&docID=176372&lang=HE',
|
||||
'info_dict': {
|
||||
'id': 's5-SiXxx1-hKh2',
|
||||
'ext': 'mp4',
|
||||
'title': 'GOALS_CELTIC_270914.mp4',
|
||||
'description': '',
|
||||
'duration': 87,
|
||||
'categories': list,
|
||||
},
|
||||
'skip': 'Blocked outside of Israel',
|
||||
}
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
media_id = mobj.group('id')
|
||||
|
||||
webpage = self._download_webpage(url, media_id)
|
||||
|
||||
video_id = self._html_search_regex('clipId=([\w-]+)', webpage, 'video id')
|
||||
|
||||
metadata = self._download_xml(
|
||||
'http://sport5-metadata-rr-d.nsacdn.com/vod/vod/%s/HDS/metadata.xml' % video_id,
|
||||
video_id)
|
||||
|
||||
error = metadata.find('./Error')
|
||||
if error is not None:
|
||||
raise ExtractorError(
|
||||
'%s returned error: %s - %s' % (
|
||||
self.IE_NAME,
|
||||
error.find('./Name').text,
|
||||
error.find('./Description').text),
|
||||
expected=True)
|
||||
|
||||
title = metadata.find('./Title').text
|
||||
description = metadata.find('./Description').text
|
||||
duration = int(metadata.find('./Duration').text)
|
||||
|
||||
posters_el = metadata.find('./PosterLinks')
|
||||
thumbnails = [{
|
||||
'url': thumbnail.text,
|
||||
'width': int(thumbnail.get('width')),
|
||||
'height': int(thumbnail.get('height')),
|
||||
} for thumbnail in posters_el.findall('./PosterIMG')] if posters_el is not None else []
|
||||
|
||||
categories_el = metadata.find('./Categories')
|
||||
categories = [
|
||||
cat.get('name') for cat in categories_el.findall('./Category')
|
||||
] if categories_el is not None else []
|
||||
|
||||
formats = [{
|
||||
'url': fmt.text,
|
||||
'ext': 'mp4',
|
||||
'vbr': int(fmt.get('bitrate')),
|
||||
'width': int(fmt.get('width')),
|
||||
'height': int(fmt.get('height')),
|
||||
} for fmt in metadata.findall('./PlaybackLinks/FileURL')]
|
||||
self._sort_formats(formats)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'thumbnails': thumbnails,
|
||||
'duration': duration,
|
||||
'categories': categories,
|
||||
'formats': formats,
|
||||
}
|
||||
@@ -19,7 +19,7 @@ class Vbox7IE(InfoExtractor):
|
||||
'md5': '99f65c0c9ef9b682b97313e052734c3f',
|
||||
'info_dict': {
|
||||
'id': '249bb972c2',
|
||||
'ext': 'flv',
|
||||
'ext': 'mp4',
|
||||
'title': 'Смях! Чудо - чист за секунди - Скрита камера',
|
||||
},
|
||||
}
|
||||
@@ -50,7 +50,6 @@ class Vbox7IE(InfoExtractor):
|
||||
return {
|
||||
'id': video_id,
|
||||
'url': final_url,
|
||||
'ext': 'flv',
|
||||
'title': title,
|
||||
'thumbnail': thumbnail_url,
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import xml.etree.ElementTree
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
compat_HTTPError,
|
||||
compat_urllib_request,
|
||||
ExtractorError,
|
||||
)
|
||||
|
||||
@@ -24,7 +25,7 @@ class VevoIE(InfoExtractor):
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'http://www.vevo.com/watch/hurts/somebody-to-die-for/GB1101300280',
|
||||
"md5": "06bea460acb744eab74a9d7dcb4bfd61",
|
||||
"md5": "95ee28ee45e70130e3ab02b0f579ae23",
|
||||
'info_dict': {
|
||||
'id': 'GB1101300280',
|
||||
'ext': 'mp4',
|
||||
@@ -40,7 +41,7 @@ class VevoIE(InfoExtractor):
|
||||
}, {
|
||||
'note': 'v3 SMIL format',
|
||||
'url': 'http://www.vevo.com/watch/cassadee-pope/i-wish-i-could-break-your-heart/USUV71302923',
|
||||
'md5': '893ec0e0d4426a1d96c01de8f2bdff58',
|
||||
'md5': 'f6ab09b034f8c22969020b042e5ac7fc',
|
||||
'info_dict': {
|
||||
'id': 'USUV71302923',
|
||||
'ext': 'mp4',
|
||||
@@ -69,6 +70,21 @@ class VevoIE(InfoExtractor):
|
||||
}]
|
||||
_SMIL_BASE_URL = 'http://smil.lvl3.vevo.com/'
|
||||
|
||||
def _real_initialize(self):
|
||||
req = compat_urllib_request.Request(
|
||||
'http://www.vevo.com/auth', data=b'')
|
||||
webpage = self._download_webpage(
|
||||
req, None,
|
||||
note='Retrieving oauth token',
|
||||
errnote='Unable to retrieve oauth token',
|
||||
fatal=False)
|
||||
if webpage is False:
|
||||
self._oauth_token = None
|
||||
else:
|
||||
self._oauth_token = self._search_regex(
|
||||
r'access_token":\s*"([^"]+)"',
|
||||
webpage, 'access token', fatal=False)
|
||||
|
||||
def _formats_from_json(self, video_info):
|
||||
last_version = {'version': -1}
|
||||
for version in video_info['videoVersions']:
|
||||
@@ -129,6 +145,26 @@ class VevoIE(InfoExtractor):
|
||||
})
|
||||
return formats
|
||||
|
||||
def _download_api_formats(self, video_id):
|
||||
if not self._oauth_token:
|
||||
self._downloader.report_warning(
|
||||
'No oauth token available, skipping API HLS download')
|
||||
return []
|
||||
|
||||
api_url = 'https://apiv2.vevo.com/video/%s/streams/hls?token=%s' % (
|
||||
video_id, self._oauth_token)
|
||||
api_data = self._download_json(
|
||||
api_url, video_id,
|
||||
note='Downloading HLS formats',
|
||||
errnote='Failed to download HLS format list', fatal=False)
|
||||
if api_data is None:
|
||||
return []
|
||||
|
||||
m3u8_url = api_data[0]['url']
|
||||
return self._extract_m3u8_formats(
|
||||
m3u8_url, video_id, entry_protocol='m3u8_native', ext='mp4',
|
||||
preference=0)
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
@@ -152,30 +188,8 @@ class VevoIE(InfoExtractor):
|
||||
else:
|
||||
age_limit = None
|
||||
|
||||
# Download SMIL
|
||||
smil_blocks = sorted((
|
||||
f for f in video_info['videoVersions']
|
||||
if f['sourceType'] == 13),
|
||||
key=lambda f: f['version'])
|
||||
|
||||
smil_url = '%s/Video/V2/VFILE/%s/%sr.smil' % (
|
||||
self._SMIL_BASE_URL, video_id, video_id.lower())
|
||||
if smil_blocks:
|
||||
smil_url_m = self._search_regex(
|
||||
r'url="([^"]+)"', smil_blocks[-1]['data'], 'SMIL URL',
|
||||
fatal=False)
|
||||
if smil_url_m is not None:
|
||||
smil_url = smil_url_m
|
||||
|
||||
try:
|
||||
smil_xml = self._download_webpage(smil_url, video_id,
|
||||
'Downloading SMIL info')
|
||||
formats.extend(self._formats_from_smil(smil_xml))
|
||||
except ExtractorError as ee:
|
||||
if not isinstance(ee.cause, compat_HTTPError):
|
||||
raise
|
||||
self._downloader.report_warning(
|
||||
'Cannot download SMIL information, falling back to JSON ..')
|
||||
# Download via HLS API
|
||||
formats.extend(self._download_api_formats(video_id))
|
||||
|
||||
self._sort_formats(formats)
|
||||
timestamp_ms = int(self._search_regex(
|
||||
|
||||
@@ -6,6 +6,7 @@ from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
int_or_none,
|
||||
compat_str,
|
||||
ExtractorError,
|
||||
)
|
||||
|
||||
|
||||
@@ -16,6 +17,24 @@ class VubeIE(InfoExtractor):
|
||||
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://vube.com/trending/William+Wei/Y8NUZ69Tf7?t=s',
|
||||
'md5': 'e7aabe1f8f1aa826b9e4735e1f9cee42',
|
||||
'info_dict': {
|
||||
'id': 'Y8NUZ69Tf7',
|
||||
'ext': 'mp4',
|
||||
'title': 'Best Drummer Ever [HD]',
|
||||
'description': 'md5:2d63c4b277b85c2277761c2cf7337d71',
|
||||
'thumbnail': 're:^https?://.*\.jpg',
|
||||
'uploader': 'William',
|
||||
'timestamp': 1406876915,
|
||||
'upload_date': '20140801',
|
||||
'duration': 258.051,
|
||||
'like_count': int,
|
||||
'dislike_count': int,
|
||||
'comment_count': int,
|
||||
'categories': ['amazing', 'hd', 'best drummer ever', 'william wei', 'bucket drumming', 'street drummer', 'epic street drumming'],
|
||||
},
|
||||
}, {
|
||||
'url': 'http://vube.com/Chiara+Grispo+Video+Channel/YL2qNPkqon',
|
||||
'md5': 'db7aba89d4603dadd627e9d1973946fe',
|
||||
'info_dict': {
|
||||
@@ -32,7 +51,8 @@ class VubeIE(InfoExtractor):
|
||||
'dislike_count': int,
|
||||
'comment_count': int,
|
||||
'categories': ['pop', 'music', 'cover', 'singing', 'jessie j', 'price tag', 'chiara grispo'],
|
||||
}
|
||||
},
|
||||
'skip': 'Removed due to DMCA',
|
||||
},
|
||||
{
|
||||
'url': 'http://vube.com/SerainaMusic/my-7-year-old-sister-and-i-singing-alive-by-krewella/UeBhTudbfS?t=s&n=1',
|
||||
@@ -51,7 +71,8 @@ class VubeIE(InfoExtractor):
|
||||
'dislike_count': int,
|
||||
'comment_count': int,
|
||||
'categories': ['seraina', 'jessica', 'krewella', 'alive'],
|
||||
}
|
||||
},
|
||||
'skip': 'Removed due to DMCA',
|
||||
}, {
|
||||
'url': 'http://vube.com/vote/Siren+Gene/0nmsMY5vEq?n=2&t=s',
|
||||
'md5': '0584fc13b50f887127d9d1007589d27f',
|
||||
@@ -69,7 +90,8 @@ class VubeIE(InfoExtractor):
|
||||
'dislike_count': int,
|
||||
'comment_count': int,
|
||||
'categories': ['let it go', 'cover', 'idina menzel', 'frozen', 'singing', 'disney', 'siren gene'],
|
||||
}
|
||||
},
|
||||
'skip': 'Removed due to DMCA',
|
||||
}
|
||||
]
|
||||
|
||||
@@ -102,6 +124,11 @@ class VubeIE(InfoExtractor):
|
||||
|
||||
self._sort_formats(formats)
|
||||
|
||||
if not formats and video.get('vst') == 'dmca':
|
||||
raise ExtractorError(
|
||||
'This video has been removed in response to a complaint received under the US Digital Millennium Copyright Act.',
|
||||
expected=True)
|
||||
|
||||
title = video['title']
|
||||
description = video.get('description')
|
||||
thumbnail = self._proto_relative_url(video.get('thumbnail_src'), scheme='http:')
|
||||
|
||||
@@ -40,6 +40,7 @@ class WatIE(InfoExtractor):
|
||||
'upload_date': '20140816',
|
||||
'duration': 2910,
|
||||
},
|
||||
'skip': "Ce contenu n'est pas disponible pour l'instant.",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import ExtractorError, compat_urllib_request
|
||||
|
||||
|
||||
class WistiaIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:fast\.)?wistia\.net/embed/iframe/(?P<id>[a-z0-9]+)'
|
||||
_API_URL = 'http://fast.wistia.com/embed/medias/{0:}.json'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://fast.wistia.net/embed/iframe/sh7fpupwlt',
|
||||
@@ -24,11 +25,13 @@ class WistiaIE(InfoExtractor):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
data_json = self._html_search_regex(
|
||||
r'Wistia\.iframeInit\((.*?), {}\);', webpage, 'video data')
|
||||
|
||||
data = json.loads(data_json)
|
||||
request = compat_urllib_request.Request(self._API_URL.format(video_id))
|
||||
request.add_header('Referer', url) # Some videos require this.
|
||||
data_json = self._download_json(request, video_id)
|
||||
if data_json.get('error'):
|
||||
raise ExtractorError('Error while getting the playlist',
|
||||
expected=True)
|
||||
data = data_json['media']
|
||||
|
||||
formats = []
|
||||
thumbnails = []
|
||||
|
||||
54
youtube_dl/extractor/ynet.py
Normal file
54
youtube_dl/extractor/ynet.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import json
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import compat_urllib_parse
|
||||
|
||||
|
||||
class YnetIE(InfoExtractor):
|
||||
_VALID_URL = r'http://(?:.+?\.)?ynet\.co\.il/(?:.+?/)?0,7340,(?P<id>L(?:-[0-9]+)+),00\.html'
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://hot.ynet.co.il/home/0,7340,L-11659-99244,00.html',
|
||||
'md5': '002b44ee2f33d50363a1c153bed524cf',
|
||||
'info_dict': {
|
||||
'id': 'L-11659-99244',
|
||||
'ext': 'flv',
|
||||
'title': 'איש לא יודע מאיפה באנו',
|
||||
'thumbnail': 're:^https?://.*\.jpg',
|
||||
}
|
||||
}, {
|
||||
'url': 'http://hot.ynet.co.il/home/0,7340,L-8859-84418,00.html',
|
||||
'md5': '6455046ae1b48cf7e2b7cae285e53a16',
|
||||
'info_dict': {
|
||||
'id': 'L-8859-84418',
|
||||
'ext': 'flv',
|
||||
'title': "צפו: הנשיקה הלוהטת של תורגי' ויוליה פלוטקין",
|
||||
'thumbnail': 're:^https?://.*\.jpg',
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
content = compat_urllib_parse.unquote_plus(self._og_search_video_url(webpage))
|
||||
config = json.loads(self._search_regex(r'config=({.+?})$', content, 'video config'))
|
||||
f4m_url = config['clip']['url']
|
||||
title = self._og_search_title(webpage)
|
||||
m = re.search(r'ynet - HOT -- (["\']+)(?P<title>.+?)\1', title)
|
||||
if m:
|
||||
title = m.group('title')
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'formats': self._extract_f4m_formats(f4m_url, video_id),
|
||||
'thumbnail': self._og_search_thumbnail(webpage),
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
# coding: utf-8
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import math
|
||||
import random
|
||||
@@ -13,18 +15,25 @@ from ..utils import (
|
||||
|
||||
|
||||
class YoukuIE(InfoExtractor):
|
||||
_VALID_URL = r'(?:(?:http://)?(?:v|player)\.youku\.com/(?:v_show/id_|player\.php/sid/)|youku:)(?P<ID>[A-Za-z0-9]+)(?:\.html|/v\.swf|)'
|
||||
_TEST = {
|
||||
u"url": u"http://v.youku.com/v_show/id_XNDgyMDQ2NTQw.html",
|
||||
u"file": u"XNDgyMDQ2NTQw_part00.flv",
|
||||
u"md5": u"ffe3f2e435663dc2d1eea34faeff5b5b",
|
||||
u"params": {u"test": False},
|
||||
u"info_dict": {
|
||||
u"title": u"youtube-dl test video \"'/\\ä↭𝕐"
|
||||
_VALID_URL = r'''(?x)
|
||||
(?:
|
||||
http://(?:v|player)\.youku\.com/(?:v_show/id_|player\.php/sid/)|
|
||||
youku:)
|
||||
(?P<id>[A-Za-z0-9]+)(?:\.html|/v\.swf|)
|
||||
'''
|
||||
_TEST = {
|
||||
'url': 'http://v.youku.com/v_show/id_XNDgyMDQ2NTQw.html',
|
||||
'md5': 'ffe3f2e435663dc2d1eea34faeff5b5b',
|
||||
'params': {
|
||||
'test': False
|
||||
},
|
||||
'info_dict': {
|
||||
'id': 'XNDgyMDQ2NTQw_part00',
|
||||
'ext': 'flv',
|
||||
'title': 'youtube-dl test video "\'/\\ä↭𝕐'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def _gen_sid(self):
|
||||
nowTime = int(time.time() * 1000)
|
||||
random1 = random.randint(1000,1998)
|
||||
@@ -55,49 +64,42 @@ class YoukuIE(InfoExtractor):
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
if mobj is None:
|
||||
raise ExtractorError(u'Invalid URL: %s' % url)
|
||||
video_id = mobj.group('ID')
|
||||
video_id = mobj.group('id')
|
||||
|
||||
info_url = 'http://v.youku.com/player/getPlayList/VideoIDS/' + video_id
|
||||
|
||||
jsondata = self._download_webpage(info_url, video_id)
|
||||
config = self._download_json(info_url, video_id)
|
||||
|
||||
self.report_extraction(video_id)
|
||||
try:
|
||||
config = json.loads(jsondata)
|
||||
error_code = config['data'][0].get('error_code')
|
||||
if error_code:
|
||||
# -8 means blocked outside China.
|
||||
error = config['data'][0].get('error') # Chinese and English, separated by newline.
|
||||
raise ExtractorError(error or u'Server reported error %i' % error_code,
|
||||
expected=True)
|
||||
error_code = config['data'][0].get('error_code')
|
||||
if error_code:
|
||||
# -8 means blocked outside China.
|
||||
error = config['data'][0].get('error') # Chinese and English, separated by newline.
|
||||
raise ExtractorError(error or 'Server reported error %i' % error_code,
|
||||
expected=True)
|
||||
|
||||
video_title = config['data'][0]['title']
|
||||
seed = config['data'][0]['seed']
|
||||
video_title = config['data'][0]['title']
|
||||
seed = config['data'][0]['seed']
|
||||
|
||||
format = self._downloader.params.get('format', None)
|
||||
supported_format = list(config['data'][0]['streamfileids'].keys())
|
||||
format = self._downloader.params.get('format', None)
|
||||
supported_format = list(config['data'][0]['streamfileids'].keys())
|
||||
|
||||
if format is None or format == 'best':
|
||||
if 'hd2' in supported_format:
|
||||
format = 'hd2'
|
||||
else:
|
||||
format = 'flv'
|
||||
ext = u'flv'
|
||||
elif format == 'worst':
|
||||
format = 'mp4'
|
||||
ext = u'mp4'
|
||||
# TODO proper format selection
|
||||
if format is None or format == 'best':
|
||||
if 'hd2' in supported_format:
|
||||
format = 'hd2'
|
||||
else:
|
||||
format = 'flv'
|
||||
ext = u'flv'
|
||||
ext = 'flv'
|
||||
elif format == 'worst':
|
||||
format = 'mp4'
|
||||
ext = 'mp4'
|
||||
else:
|
||||
format = 'flv'
|
||||
ext = 'flv'
|
||||
|
||||
|
||||
fileid = config['data'][0]['streamfileids'][format]
|
||||
keys = [s['k'] for s in config['data'][0]['segs'][format]]
|
||||
# segs is usually a dictionary, but an empty *list* if an error occured.
|
||||
except (UnicodeDecodeError, ValueError, KeyError):
|
||||
raise ExtractorError(u'Unable to extract info section')
|
||||
fileid = config['data'][0]['streamfileids'][format]
|
||||
keys = [s['k'] for s in config['data'][0]['segs'][format]]
|
||||
# segs is usually a dictionary, but an empty *list* if an error occured.
|
||||
|
||||
files_info=[]
|
||||
sid = self._gen_sid()
|
||||
@@ -106,9 +108,8 @@ class YoukuIE(InfoExtractor):
|
||||
#column 8,9 of fileid represent the segment number
|
||||
#fileid[7:9] should be changed
|
||||
for index, key in enumerate(keys):
|
||||
|
||||
temp_fileid = '%s%02X%s' % (fileid[0:8], index, fileid[10:])
|
||||
download_url = 'http://f.youku.com/player/getFlvPath/sid/%s_%02X/st/flv/fileid/%s?k=%s' % (sid, index, temp_fileid, key)
|
||||
download_url = 'http://k.youku.com/player/getFlvPath/sid/%s_%02X/st/flv/fileid/%s?k=%s' % (sid, index, temp_fileid, key)
|
||||
|
||||
info = {
|
||||
'id': '%s_part%02d' % (video_id, index),
|
||||
|
||||
@@ -1068,6 +1068,13 @@ class YoutubePlaylistIE(YoutubeBaseInfoExtractor):
|
||||
'info_dict': {
|
||||
'title': 'JODA15',
|
||||
}
|
||||
}, {
|
||||
'note': 'Embedded SWF player',
|
||||
'url': 'http://www.youtube.com/p/YN5VISEtHet5D4NEvfTd0zcgFk84NqFZ?hl=en_US&fs=1&rel=0',
|
||||
'playlist_count': 4,
|
||||
'info_dict': {
|
||||
'title': 'JODA7',
|
||||
}
|
||||
}]
|
||||
|
||||
def _real_initialize(self):
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
|
||||
__version__ = '2014.09.24'
|
||||
__version__ = '2014.09.28'
|
||||
|
||||
Reference in New Issue
Block a user