Compare commits
72 Commits
2014.02.10
...
2014.02.19
Author | SHA1 | Date | |
---|---|---|---|
882907a818 | |||
572a89cc4e | |||
c377110539 | |||
a9c7198a0b | |||
f6f01ea17b | |||
f2d0fc6823 | |||
f7000f3a1b | |||
c7f0177fa7 | |||
09c4d50944 | |||
2eb5d315d4 | |||
ad5976b4d9 | |||
a0dfcdce5e | |||
96d1637082 | |||
960f317171 | |||
4412ca751d | |||
cbffec0c95 | |||
0cea52cc18 | |||
6d784e87f4 | |||
ae6cae78f1 | |||
0f99566c01 | |||
2db806b4aa | |||
3f32c0ba4c | |||
541cb26c0d | |||
5544e038ab | |||
9032dc28a6 | |||
03635e2a71 | |||
00cf938aa5 | |||
a5f707c495 | |||
1824b48169 | |||
07ad22b8af | |||
b53466e168 | |||
6a7a389679 | |||
4edff78531 | |||
99043c2ea5 | |||
e68abba910 | |||
3165dc4d9f | |||
66c43a53e4 | |||
463b334616 | |||
b71dbc57c4 | |||
72ca1d7f45 | |||
76e461f395 | |||
1074982e6e | |||
29b2aaf035 | |||
6f90d098c5 | |||
0715161450 | |||
896583517f | |||
713d31fac8 | |||
96cb10a5f5 | |||
c207c1044e | |||
79629ec717 | |||
008fda0f08 | |||
0ae6b01937 | |||
def630e523 | |||
c5ba203e23 | |||
2317e6b2b3 | |||
cb38928974 | |||
fa78f13302 | |||
18395217c4 | |||
34bd987811 | |||
af6ba6a1c4 | |||
85409a0c69 | |||
ebfe352b62 | |||
fde56d2f17 | |||
3501423dfe | |||
0de668af51 | |||
2a584ea90a | |||
0f6ed94a15 | |||
bcb891e82b | |||
ac6e4ca1ed | |||
0793a7b3c7 | |||
cf1eb45153 | |||
a97bcd80ba |
14
README.md
14
README.md
@ -281,12 +281,14 @@ Videos can be filtered by their upload date using the options `--date`, `--dateb
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
$ # Download only the videos uploaded in the last 6 months
|
# Download only the videos uploaded in the last 6 months
|
||||||
$ youtube-dl --dateafter now-6months
|
$ youtube-dl --dateafter now-6months
|
||||||
$ # Download only the videos uploaded on January 1, 1970
|
|
||||||
$ youtube-dl --date 19700101
|
# Download only the videos uploaded on January 1, 1970
|
||||||
$ # will only download the videos uploaded in the 200x decade
|
$ youtube-dl --date 19700101
|
||||||
$ youtube-dl --dateafter 20000101 --datebefore 20091231
|
|
||||||
|
$ # will only download the videos uploaded in the 200x decade
|
||||||
|
$ youtube-dl --dateafter 20000101 --datebefore 20091231
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
|
|
||||||
|
@ -68,6 +68,9 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
def test_youtube_show_matching(self):
|
def test_youtube_show_matching(self):
|
||||||
self.assertMatch('http://www.youtube.com/show/airdisasters', ['youtube:show'])
|
self.assertMatch('http://www.youtube.com/show/airdisasters', ['youtube:show'])
|
||||||
|
|
||||||
|
def test_youtube_truncated(self):
|
||||||
|
self.assertMatch('http://www.youtube.com/watch?', ['youtube:truncated_url'])
|
||||||
|
|
||||||
def test_justin_tv_channelid_matching(self):
|
def test_justin_tv_channelid_matching(self):
|
||||||
self.assertTrue(JustinTVIE.suitable(u"justin.tv/vanillatv"))
|
self.assertTrue(JustinTVIE.suitable(u"justin.tv/vanillatv"))
|
||||||
self.assertTrue(JustinTVIE.suitable(u"twitch.tv/vanillatv"))
|
self.assertTrue(JustinTVIE.suitable(u"twitch.tv/vanillatv"))
|
||||||
|
@ -55,10 +55,10 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
def test_dailymotion_user(self):
|
def test_dailymotion_user(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = DailymotionUserIE(dl)
|
ie = DailymotionUserIE(dl)
|
||||||
result = ie.extract('http://www.dailymotion.com/user/generation-quoi/')
|
result = ie.extract('https://www.dailymotion.com/user/nqtv')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['title'], 'Génération Quoi')
|
self.assertEqual(result['title'], 'Rémi Gaillard')
|
||||||
self.assertTrue(len(result['entries']) >= 26)
|
self.assertTrue(len(result['entries']) >= 100)
|
||||||
|
|
||||||
def test_vimeo_channel(self):
|
def test_vimeo_channel(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
|
@ -25,6 +25,7 @@ from youtube_dl.utils import (
|
|||||||
shell_quote,
|
shell_quote,
|
||||||
smuggle_url,
|
smuggle_url,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
|
struct_unpack,
|
||||||
timeconvert,
|
timeconvert,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
@ -201,7 +202,16 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(parse_duration('1'), 1)
|
self.assertEqual(parse_duration('1'), 1)
|
||||||
self.assertEqual(parse_duration('1337:12'), 80232)
|
self.assertEqual(parse_duration('1337:12'), 80232)
|
||||||
self.assertEqual(parse_duration('9:12:43'), 33163)
|
self.assertEqual(parse_duration('9:12:43'), 33163)
|
||||||
|
self.assertEqual(parse_duration('12:00'), 720)
|
||||||
|
self.assertEqual(parse_duration('00:01:01'), 61)
|
||||||
self.assertEqual(parse_duration('x:y'), None)
|
self.assertEqual(parse_duration('x:y'), None)
|
||||||
|
self.assertEqual(parse_duration('3h11m53s'), 11513)
|
||||||
|
self.assertEqual(parse_duration('62m45s'), 3765)
|
||||||
|
self.assertEqual(parse_duration('6m59s'), 419)
|
||||||
|
self.assertEqual(parse_duration('49s'), 49)
|
||||||
|
self.assertEqual(parse_duration('0h0m0s'), 0)
|
||||||
|
self.assertEqual(parse_duration('0m0s'), 0)
|
||||||
|
self.assertEqual(parse_duration('0s'), 0)
|
||||||
|
|
||||||
def test_fix_xml_ampersands(self):
|
def test_fix_xml_ampersands(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@ -237,5 +247,8 @@ class TestUtil(unittest.TestCase):
|
|||||||
testPL(5, 2, (2, 99), [2, 3, 4])
|
testPL(5, 2, (2, 99), [2, 3, 4])
|
||||||
testPL(5, 2, (20, 99), [])
|
testPL(5, 2, (20, 99), [])
|
||||||
|
|
||||||
|
def test_struct_unpack(self):
|
||||||
|
self.assertEqual(struct_unpack(u'!B', b'\x00'), (0,))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -41,7 +41,11 @@ __authors__ = (
|
|||||||
'Chris Gahan',
|
'Chris Gahan',
|
||||||
'Saimadhav Heblikar',
|
'Saimadhav Heblikar',
|
||||||
'Mike Col',
|
'Mike Col',
|
||||||
|
'Oleg Prutz',
|
||||||
|
'pulpe',
|
||||||
'Andreas Schmitz',
|
'Andreas Schmitz',
|
||||||
|
'Michael Kaiser',
|
||||||
|
'Niklas Laxström',
|
||||||
)
|
)
|
||||||
|
|
||||||
__license__ = 'Public Domain'
|
__license__ = 'Public Domain'
|
||||||
|
@ -5,6 +5,7 @@ from .hls import HlsFD
|
|||||||
from .http import HttpFD
|
from .http import HttpFD
|
||||||
from .mplayer import MplayerFD
|
from .mplayer import MplayerFD
|
||||||
from .rtmp import RtmpFD
|
from .rtmp import RtmpFD
|
||||||
|
from .f4m import F4mFD
|
||||||
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_ext,
|
||||||
@ -22,5 +23,7 @@ def get_suitable_downloader(info_dict):
|
|||||||
return HlsFD
|
return HlsFD
|
||||||
if url.startswith('mms') or url.startswith('rtsp'):
|
if url.startswith('mms') or url.startswith('rtsp'):
|
||||||
return MplayerFD
|
return MplayerFD
|
||||||
|
if determine_ext(url) == 'f4m':
|
||||||
|
return F4mFD
|
||||||
else:
|
else:
|
||||||
return HttpFD
|
return HttpFD
|
||||||
|
315
youtube_dl/downloader/f4m.py
Normal file
315
youtube_dl/downloader/f4m.py
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import io
|
||||||
|
import itertools
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
|
from .common import FileDownloader
|
||||||
|
from .http import HttpFD
|
||||||
|
from ..utils import (
|
||||||
|
struct_pack,
|
||||||
|
struct_unpack,
|
||||||
|
compat_urllib_request,
|
||||||
|
compat_urlparse,
|
||||||
|
format_bytes,
|
||||||
|
encodeFilename,
|
||||||
|
sanitize_open,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FlvReader(io.BytesIO):
|
||||||
|
"""
|
||||||
|
Reader for Flv files
|
||||||
|
The file format is documented in https://www.adobe.com/devnet/f4v.html
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Utility functions for reading numbers and strings
|
||||||
|
def read_unsigned_long_long(self):
|
||||||
|
return struct_unpack('!Q', self.read(8))[0]
|
||||||
|
|
||||||
|
def read_unsigned_int(self):
|
||||||
|
return struct_unpack('!I', self.read(4))[0]
|
||||||
|
|
||||||
|
def read_unsigned_char(self):
|
||||||
|
return struct_unpack('!B', self.read(1))[0]
|
||||||
|
|
||||||
|
def read_string(self):
|
||||||
|
res = b''
|
||||||
|
while True:
|
||||||
|
char = self.read(1)
|
||||||
|
if char == b'\x00':
|
||||||
|
break
|
||||||
|
res += char
|
||||||
|
return res
|
||||||
|
|
||||||
|
def read_box_info(self):
|
||||||
|
"""
|
||||||
|
Read a box and return the info as a tuple: (box_size, box_type, box_data)
|
||||||
|
"""
|
||||||
|
real_size = size = self.read_unsigned_int()
|
||||||
|
box_type = self.read(4)
|
||||||
|
header_end = 8
|
||||||
|
if size == 1:
|
||||||
|
real_size = self.read_unsigned_long_long()
|
||||||
|
header_end = 16
|
||||||
|
return real_size, box_type, self.read(real_size-header_end)
|
||||||
|
|
||||||
|
def read_asrt(self):
|
||||||
|
# version
|
||||||
|
self.read_unsigned_char()
|
||||||
|
# flags
|
||||||
|
self.read(3)
|
||||||
|
quality_entry_count = self.read_unsigned_char()
|
||||||
|
# QualityEntryCount
|
||||||
|
for i in range(quality_entry_count):
|
||||||
|
self.read_string()
|
||||||
|
|
||||||
|
segment_run_count = self.read_unsigned_int()
|
||||||
|
segments = []
|
||||||
|
for i in range(segment_run_count):
|
||||||
|
first_segment = self.read_unsigned_int()
|
||||||
|
fragments_per_segment = self.read_unsigned_int()
|
||||||
|
segments.append((first_segment, fragments_per_segment))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'segment_run': segments,
|
||||||
|
}
|
||||||
|
|
||||||
|
def read_afrt(self):
|
||||||
|
# version
|
||||||
|
self.read_unsigned_char()
|
||||||
|
# flags
|
||||||
|
self.read(3)
|
||||||
|
# time scale
|
||||||
|
self.read_unsigned_int()
|
||||||
|
|
||||||
|
quality_entry_count = self.read_unsigned_char()
|
||||||
|
# QualitySegmentUrlModifiers
|
||||||
|
for i in range(quality_entry_count):
|
||||||
|
self.read_string()
|
||||||
|
|
||||||
|
fragments_count = self.read_unsigned_int()
|
||||||
|
fragments = []
|
||||||
|
for i in range(fragments_count):
|
||||||
|
first = self.read_unsigned_int()
|
||||||
|
first_ts = self.read_unsigned_long_long()
|
||||||
|
duration = self.read_unsigned_int()
|
||||||
|
if duration == 0:
|
||||||
|
discontinuity_indicator = self.read_unsigned_char()
|
||||||
|
else:
|
||||||
|
discontinuity_indicator = None
|
||||||
|
fragments.append({
|
||||||
|
'first': first,
|
||||||
|
'ts': first_ts,
|
||||||
|
'duration': duration,
|
||||||
|
'discontinuity_indicator': discontinuity_indicator,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'fragments': fragments,
|
||||||
|
}
|
||||||
|
|
||||||
|
def read_abst(self):
|
||||||
|
# version
|
||||||
|
self.read_unsigned_char()
|
||||||
|
# flags
|
||||||
|
self.read(3)
|
||||||
|
# BootstrapinfoVersion
|
||||||
|
bootstrap_info_version = self.read_unsigned_int()
|
||||||
|
# Profile,Live,Update,Reserved
|
||||||
|
self.read(1)
|
||||||
|
# time scale
|
||||||
|
self.read_unsigned_int()
|
||||||
|
# CurrentMediaTime
|
||||||
|
self.read_unsigned_long_long()
|
||||||
|
# SmpteTimeCodeOffset
|
||||||
|
self.read_unsigned_long_long()
|
||||||
|
# MovieIdentifier
|
||||||
|
movie_identifier = self.read_string()
|
||||||
|
server_count = self.read_unsigned_char()
|
||||||
|
# ServerEntryTable
|
||||||
|
for i in range(server_count):
|
||||||
|
self.read_string()
|
||||||
|
quality_count = self.read_unsigned_char()
|
||||||
|
# QualityEntryTable
|
||||||
|
for i in range(server_count):
|
||||||
|
self.read_string()
|
||||||
|
# DrmData
|
||||||
|
self.read_string()
|
||||||
|
# MetaData
|
||||||
|
self.read_string()
|
||||||
|
|
||||||
|
segments_count = self.read_unsigned_char()
|
||||||
|
segments = []
|
||||||
|
for i in range(segments_count):
|
||||||
|
box_size, box_type, box_data = self.read_box_info()
|
||||||
|
assert box_type == b'asrt'
|
||||||
|
segment = FlvReader(box_data).read_asrt()
|
||||||
|
segments.append(segment)
|
||||||
|
fragments_run_count = self.read_unsigned_char()
|
||||||
|
fragments = []
|
||||||
|
for i in range(fragments_run_count):
|
||||||
|
box_size, box_type, box_data = self.read_box_info()
|
||||||
|
assert box_type == b'afrt'
|
||||||
|
fragments.append(FlvReader(box_data).read_afrt())
|
||||||
|
|
||||||
|
return {
|
||||||
|
'segments': segments,
|
||||||
|
'fragments': fragments,
|
||||||
|
}
|
||||||
|
|
||||||
|
def read_bootstrap_info(self):
|
||||||
|
total_size, box_type, box_data = self.read_box_info()
|
||||||
|
assert box_type == b'abst'
|
||||||
|
return FlvReader(box_data).read_abst()
|
||||||
|
|
||||||
|
|
||||||
|
def read_bootstrap_info(bootstrap_bytes):
|
||||||
|
return FlvReader(bootstrap_bytes).read_bootstrap_info()
|
||||||
|
|
||||||
|
|
||||||
|
def build_fragments_list(boot_info):
|
||||||
|
""" Return a list of (segment, fragment) for each fragment in the video """
|
||||||
|
res = []
|
||||||
|
segment_run_table = boot_info['segments'][0]
|
||||||
|
# I've only found videos with one segment
|
||||||
|
segment_run_entry = segment_run_table['segment_run'][0]
|
||||||
|
n_frags = segment_run_entry[1]
|
||||||
|
fragment_run_entry_table = boot_info['fragments'][0]['fragments']
|
||||||
|
first_frag_number = fragment_run_entry_table[0]['first']
|
||||||
|
for (i, frag_number) in zip(range(1, n_frags+1), itertools.count(first_frag_number)):
|
||||||
|
res.append((1, frag_number))
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def write_flv_header(stream, metadata):
|
||||||
|
"""Writes the FLV header and the metadata to stream"""
|
||||||
|
# FLV header
|
||||||
|
stream.write(b'FLV\x01')
|
||||||
|
stream.write(b'\x05')
|
||||||
|
stream.write(b'\x00\x00\x00\x09')
|
||||||
|
# FLV File body
|
||||||
|
stream.write(b'\x00\x00\x00\x00')
|
||||||
|
# FLVTAG
|
||||||
|
# Script data
|
||||||
|
stream.write(b'\x12')
|
||||||
|
# Size of the metadata with 3 bytes
|
||||||
|
stream.write(struct_pack('!L', len(metadata))[1:])
|
||||||
|
stream.write(b'\x00\x00\x00\x00\x00\x00\x00')
|
||||||
|
stream.write(metadata)
|
||||||
|
# Magic numbers extracted from the output files produced by AdobeHDS.php
|
||||||
|
#(https://github.com/K-S-V/Scripts)
|
||||||
|
stream.write(b'\x00\x00\x01\x73')
|
||||||
|
|
||||||
|
|
||||||
|
def _add_ns(prop):
|
||||||
|
return '{http://ns.adobe.com/f4m/1.0}%s' % prop
|
||||||
|
|
||||||
|
|
||||||
|
class HttpQuietDownloader(HttpFD):
|
||||||
|
def to_screen(self, *args, **kargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class F4mFD(FileDownloader):
|
||||||
|
"""
|
||||||
|
A downloader for f4m manifests or AdobeHDS.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def real_download(self, filename, info_dict):
|
||||||
|
man_url = info_dict['url']
|
||||||
|
self.to_screen('[download] Downloading f4m manifest')
|
||||||
|
manifest = self.ydl.urlopen(man_url).read()
|
||||||
|
self.report_destination(filename)
|
||||||
|
http_dl = HttpQuietDownloader(self.ydl,
|
||||||
|
{
|
||||||
|
'continuedl': True,
|
||||||
|
'quiet': True,
|
||||||
|
'noprogress': True,
|
||||||
|
'test': self.params.get('test', False),
|
||||||
|
})
|
||||||
|
|
||||||
|
doc = etree.fromstring(manifest)
|
||||||
|
formats = [(int(f.attrib.get('bitrate', -1)), f) for f in doc.findall(_add_ns('media'))]
|
||||||
|
formats = sorted(formats, key=lambda f: f[0])
|
||||||
|
rate, media = formats[-1]
|
||||||
|
base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
|
||||||
|
bootstrap = base64.b64decode(doc.find(_add_ns('bootstrapInfo')).text)
|
||||||
|
metadata = base64.b64decode(media.find(_add_ns('metadata')).text)
|
||||||
|
boot_info = read_bootstrap_info(bootstrap)
|
||||||
|
fragments_list = build_fragments_list(boot_info)
|
||||||
|
if self.params.get('test', False):
|
||||||
|
# We only download the first fragment
|
||||||
|
fragments_list = fragments_list[:1]
|
||||||
|
total_frags = len(fragments_list)
|
||||||
|
|
||||||
|
tmpfilename = self.temp_name(filename)
|
||||||
|
(dest_stream, tmpfilename) = sanitize_open(tmpfilename, 'wb')
|
||||||
|
write_flv_header(dest_stream, metadata)
|
||||||
|
|
||||||
|
# This dict stores the download progress, it's updated by the progress
|
||||||
|
# hook
|
||||||
|
state = {
|
||||||
|
'downloaded_bytes': 0,
|
||||||
|
'frag_counter': 0,
|
||||||
|
}
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
def frag_progress_hook(status):
|
||||||
|
frag_total_bytes = status.get('total_bytes', 0)
|
||||||
|
estimated_size = (state['downloaded_bytes'] +
|
||||||
|
(total_frags - state['frag_counter']) * frag_total_bytes)
|
||||||
|
if status['status'] == 'finished':
|
||||||
|
state['downloaded_bytes'] += frag_total_bytes
|
||||||
|
state['frag_counter'] += 1
|
||||||
|
progress = self.calc_percent(state['frag_counter'], total_frags)
|
||||||
|
byte_counter = state['downloaded_bytes']
|
||||||
|
else:
|
||||||
|
frag_downloaded_bytes = status['downloaded_bytes']
|
||||||
|
byte_counter = state['downloaded_bytes'] + frag_downloaded_bytes
|
||||||
|
frag_progress = self.calc_percent(frag_downloaded_bytes,
|
||||||
|
frag_total_bytes)
|
||||||
|
progress = self.calc_percent(state['frag_counter'], total_frags)
|
||||||
|
progress += frag_progress / float(total_frags)
|
||||||
|
|
||||||
|
eta = self.calc_eta(start, time.time(), estimated_size, byte_counter)
|
||||||
|
self.report_progress(progress, format_bytes(estimated_size),
|
||||||
|
status.get('speed'), eta)
|
||||||
|
http_dl.add_progress_hook(frag_progress_hook)
|
||||||
|
|
||||||
|
frags_filenames = []
|
||||||
|
for (seg_i, frag_i) in fragments_list:
|
||||||
|
name = 'Seg%d-Frag%d' % (seg_i, frag_i)
|
||||||
|
url = base_url + name
|
||||||
|
frag_filename = '%s-%s' % (tmpfilename, name)
|
||||||
|
success = http_dl.download(frag_filename, {'url': url})
|
||||||
|
if not success:
|
||||||
|
return False
|
||||||
|
with open(frag_filename, 'rb') as down:
|
||||||
|
down_data = down.read()
|
||||||
|
reader = FlvReader(down_data)
|
||||||
|
while True:
|
||||||
|
_, box_type, box_data = reader.read_box_info()
|
||||||
|
if box_type == b'mdat':
|
||||||
|
dest_stream.write(box_data)
|
||||||
|
break
|
||||||
|
frags_filenames.append(frag_filename)
|
||||||
|
|
||||||
|
self.report_finish(format_bytes(state['downloaded_bytes']), time.time() - start)
|
||||||
|
|
||||||
|
self.try_rename(tmpfilename, filename)
|
||||||
|
for frag_file in frags_filenames:
|
||||||
|
os.remove(frag_file)
|
||||||
|
|
||||||
|
fsize = os.path.getsize(encodeFilename(filename))
|
||||||
|
self._hook_progress({
|
||||||
|
'downloaded_bytes': fsize,
|
||||||
|
'total_bytes': fsize,
|
||||||
|
'filename': filename,
|
||||||
|
'status': 'finished',
|
||||||
|
})
|
||||||
|
|
||||||
|
return True
|
@ -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,11 +67,13 @@ 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,
|
||||||
)
|
)
|
||||||
from .flickr import FlickrIE
|
from .flickr import FlickrIE
|
||||||
|
from .fourtube import FourTubeIE
|
||||||
from .franceinter import FranceInterIE
|
from .franceinter import FranceInterIE
|
||||||
from .francetv import (
|
from .francetv import (
|
||||||
PluzzIE,
|
PluzzIE,
|
||||||
@ -87,6 +92,7 @@ from .generic import GenericIE
|
|||||||
from .googleplus import GooglePlusIE
|
from .googleplus import GooglePlusIE
|
||||||
from .googlesearch import GoogleSearchIE
|
from .googlesearch import GoogleSearchIE
|
||||||
from .hark import HarkIE
|
from .hark import HarkIE
|
||||||
|
from .helsinki import HelsinkiIE
|
||||||
from .hotnewhiphop import HotNewHipHopIE
|
from .hotnewhiphop import HotNewHipHopIE
|
||||||
from .howcast import HowcastIE
|
from .howcast import HowcastIE
|
||||||
from .huffpost import HuffPostIE
|
from .huffpost import HuffPostIE
|
||||||
@ -204,10 +210,13 @@ 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 .syfy import SyfyIE
|
||||||
from .sztvhu import SztvHuIE
|
from .sztvhu import SztvHuIE
|
||||||
from .teamcoco import TeamcocoIE
|
from .teamcoco import TeamcocoIE
|
||||||
from .techtalks import TechTalksIE
|
from .techtalks import TechTalksIE
|
||||||
from .ted import TEDIE
|
from .ted import TEDIE
|
||||||
|
from .testurl import TestURLIE
|
||||||
from .tf1 import TF1IE
|
from .tf1 import TF1IE
|
||||||
from .theplatform import ThePlatformIE
|
from .theplatform import ThePlatformIE
|
||||||
from .thisav import ThisAVIE
|
from .thisav import ThisAVIE
|
||||||
@ -225,6 +234,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
|
||||||
|
@ -13,13 +13,13 @@ class BBCCoUkIE(SubtitlesInfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://www.bbc.co.uk/programmes/p01q7wz1',
|
'url': 'http://www.bbc.co.uk/programmes/b039g8p7',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'p01q7wz4',
|
'id': 'b039d07m',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Friction: Blu Mar Ten guest mix: Blu Mar Ten - Guest Mix',
|
'title': 'Kaleidoscope: Leonard Cohen',
|
||||||
'description': 'Blu Mar Ten deliver a Guest Mix for Friction.',
|
'description': 'md5:db4755d7a665ae72343779f7dacb402c',
|
||||||
'duration': 1936,
|
'duration': 1740,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
# rtmp download
|
||||||
@ -38,7 +38,8 @@ class BBCCoUkIE(SubtitlesInfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
# rtmp download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
},
|
||||||
|
'skip': 'Episode is no longer available on BBC iPlayer Radio',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://www.bbc.co.uk/iplayer/episode/b03vhd1f/The_Voice_UK_Series_3_Blind_Auditions_5/',
|
'url': 'http://www.bbc.co.uk/iplayer/episode/b03vhd1f/The_Voice_UK_Series_3_Blind_Auditions_5/',
|
||||||
|
@ -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'],
|
||||||
}]
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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'
|
||||||
|
@ -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(),
|
||||||
|
}
|
||||||
|
@ -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': {
|
||||||
|
@ -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),
|
||||||
}]
|
}
|
||||||
|
@ -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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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'],
|
||||||
}]
|
}
|
||||||
|
60
youtube_dl/extractor/firsttv.py
Normal file
60
youtube_dl/extractor/firsttv.py
Normal 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> </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),
|
||||||
|
}
|
95
youtube_dl/extractor/fourtube.py
Normal file
95
youtube_dl/extractor/fourtube.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_urllib_request,
|
||||||
|
unified_strdate,
|
||||||
|
str_to_int,
|
||||||
|
parse_duration,
|
||||||
|
)
|
||||||
|
from youtube_dl.utils import clean_html
|
||||||
|
|
||||||
|
|
||||||
|
class FourTubeIE(InfoExtractor):
|
||||||
|
IE_NAME = '4tube'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?4tube\.com/videos/(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.4tube.com/videos/209733/hot-babe-holly-michaels-gets-her-ass-stuffed-by-black',
|
||||||
|
'md5': '6516c8ac63b03de06bc8eac14362db4f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '209733',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Hot Babe Holly Michaels gets her ass stuffed by black',
|
||||||
|
'uploader': 'WCP Club',
|
||||||
|
'uploader_id': 'wcp-club',
|
||||||
|
'upload_date': '20131031',
|
||||||
|
'duration': 583,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
webpage_url = 'http://www.4tube.com/videos/' + video_id
|
||||||
|
webpage = self._download_webpage(webpage_url, video_id)
|
||||||
|
|
||||||
|
self.report_extraction(video_id)
|
||||||
|
|
||||||
|
playlist_json = self._html_search_regex(r'var playerConfigPlaylist\s+=\s+([^;]+)', webpage, 'Playlist')
|
||||||
|
media_id = self._search_regex(r'idMedia:\s*(\d+)', playlist_json, 'Media Id')
|
||||||
|
sources = self._search_regex(r'sources:\s*\[([^\]]*)\]', playlist_json, 'Sources').split(',')
|
||||||
|
title = self._search_regex(r'title:\s*"([^"]*)', playlist_json, 'Title')
|
||||||
|
thumbnail_url = self._search_regex(r'image:\s*"([^"]*)', playlist_json, 'Thumbnail', fatal=False)
|
||||||
|
|
||||||
|
uploader_str = self._search_regex(r'<span>Uploaded by</span>(.*?)<span>', webpage, 'uploader', fatal=False)
|
||||||
|
mobj = re.search(r'<a href="/sites/(?P<id>[^"]+)"><strong>(?P<name>[^<]+)</strong></a>', uploader_str)
|
||||||
|
(uploader, uploader_id) = (mobj.group('name'), mobj.group('id')) if mobj else (clean_html(uploader_str), None)
|
||||||
|
|
||||||
|
upload_date = None
|
||||||
|
view_count = None
|
||||||
|
duration = None
|
||||||
|
description = self._html_search_meta('description', webpage, 'description')
|
||||||
|
if description:
|
||||||
|
upload_date = self._search_regex(r'Published Date: (\d{2} [a-zA-Z]{3} \d{4})', description, 'upload date',
|
||||||
|
fatal=False)
|
||||||
|
if upload_date:
|
||||||
|
upload_date = unified_strdate(upload_date)
|
||||||
|
view_count = self._search_regex(r'Views: ([\d,\.]+)', description, 'view count', fatal=False)
|
||||||
|
if view_count:
|
||||||
|
view_count = str_to_int(view_count)
|
||||||
|
duration = parse_duration(self._search_regex(r'Length: (\d+m\d+s)', description, 'duration', fatal=False))
|
||||||
|
|
||||||
|
token_url = "http://tkn.4tube.com/{0}/desktop/{1}".format(media_id, "+".join(sources))
|
||||||
|
headers = {
|
||||||
|
b'Content-Type': b'application/x-www-form-urlencoded',
|
||||||
|
b'Origin': b'http://www.4tube.com',
|
||||||
|
}
|
||||||
|
token_req = compat_urllib_request.Request(token_url, b'{}', headers)
|
||||||
|
tokens = self._download_json(token_req, video_id)
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'url': tokens[format]['token'],
|
||||||
|
'format_id': format + 'p',
|
||||||
|
'resolution': format + 'p',
|
||||||
|
'quality': int(format),
|
||||||
|
} for format in sources]
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'formats': formats,
|
||||||
|
'thumbnail': thumbnail_url,
|
||||||
|
'uploader': uploader,
|
||||||
|
'uploader_id': uploader_id,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
'view_count': view_count,
|
||||||
|
'duration': duration,
|
||||||
|
'age_limit': 18,
|
||||||
|
'webpage_url': webpage_url,
|
||||||
|
}
|
@ -184,6 +184,7 @@ class GenerationQuoiIE(InfoExtractor):
|
|||||||
# It uses Dailymotion
|
# It uses Dailymotion
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
|
'skip': 'Only available from France',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -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,
|
||||||
}]
|
}
|
||||||
|
@ -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!',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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',
|
||||||
}]
|
}
|
||||||
|
62
youtube_dl/extractor/helsinki.py
Normal file
62
youtube_dl/extractor/helsinki.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class HelsinkiIE(InfoExtractor):
|
||||||
|
IE_DESC = 'helsinki.fi'
|
||||||
|
_VALID_URL = r'https?://video\.helsinki\.fi/Arkisto/flash\.php\?id=(?P<id>\d+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://video.helsinki.fi/Arkisto/flash.php?id=20258',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '20258',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Tietotekniikkafoorumi-iltapäivä',
|
||||||
|
'description': 'md5:f5c904224d43c133225130fe156a5ee0',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True, # RTMP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
mobj = re.search(r'file=((\w+):[^&]+)', webpage)
|
||||||
|
if mobj:
|
||||||
|
formats.append({
|
||||||
|
'ext': mobj.group(2),
|
||||||
|
'play_path': mobj.group(1),
|
||||||
|
'url': 'rtmp://flashvideo.it.helsinki.fi/vod/',
|
||||||
|
'player_url': 'http://video.helsinki.fi/player.swf',
|
||||||
|
'format_note': 'sd',
|
||||||
|
'quality': 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
mobj = re.search(r'hd\.file=((\w+):[^&]+)', webpage)
|
||||||
|
if mobj:
|
||||||
|
formats.append({
|
||||||
|
'ext': mobj.group(2),
|
||||||
|
'play_path': mobj.group(1),
|
||||||
|
'url': 'rtmp://flashvideo.it.helsinki.fi/vod/',
|
||||||
|
'player_url': 'http://video.helsinki.fi/player.swf',
|
||||||
|
'format_note': 'hd',
|
||||||
|
'quality': 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': self._og_search_title(webpage).replace('Video: ', ''),
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -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),
|
||||||
}]
|
}
|
||||||
|
@ -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,
|
||||||
}]
|
}
|
||||||
|
@ -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
|
||||||
|
@ -4,7 +4,10 @@ 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):
|
||||||
@ -14,9 +17,10 @@ class LifeNewsIE(InfoExtractor):
|
|||||||
|
|
||||||
_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',
|
||||||
@ -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,
|
||||||
}
|
}
|
@ -4,15 +4,17 @@ import json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
class LiveLeakIE(InfoExtractor):
|
class LiveLeakIE(InfoExtractor):
|
||||||
_VALID_URL = r'^(?:http://)?(?:\w+\.)?liveleak\.com/view\?(?:.*?)i=(?P<video_id>[\w_]+)(?:.*)'
|
_VALID_URL = r'^(?:http://)?(?:\w+\.)?liveleak\.com/view\?(?:.*?)i=(?P<video_id>[\w_]+)(?:.*)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.liveleak.com/view?i=757_1364311680',
|
'url': 'http://www.liveleak.com/view?i=757_1364311680',
|
||||||
'file': '757_1364311680.mp4',
|
|
||||||
'md5': '0813c2430bea7a46bf13acf3406992f4',
|
'md5': '0813c2430bea7a46bf13acf3406992f4',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '757_1364311680',
|
||||||
|
'ext': 'mp4',
|
||||||
'description': 'extremely bad day for this guy..!',
|
'description': 'extremely bad day for this guy..!',
|
||||||
'uploader': 'ljfriel2',
|
'uploader': 'ljfriel2',
|
||||||
'title': 'Most unlucky car accident'
|
'title': 'Most unlucky car accident'
|
||||||
@ -20,25 +22,62 @@ class LiveLeakIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://www.liveleak.com/view?i=f93_1390833151',
|
'url': 'http://www.liveleak.com/view?i=f93_1390833151',
|
||||||
'file': 'f93_1390833151.mp4',
|
|
||||||
'md5': 'd3f1367d14cc3c15bf24fbfbe04b9abf',
|
'md5': 'd3f1367d14cc3c15bf24fbfbe04b9abf',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': 'f93_1390833151',
|
||||||
|
'ext': 'mp4',
|
||||||
'description': 'German Television Channel NDR does an exclusive interview with Edward Snowden.\r\nUploaded on LiveLeak cause German Television thinks the rest of the world isn\'t intereseted in Edward Snowden.',
|
'description': 'German Television Channel NDR does an exclusive interview with Edward Snowden.\r\nUploaded on LiveLeak cause German Television thinks the rest of the world isn\'t intereseted in Edward Snowden.',
|
||||||
'uploader': 'ARD_Stinkt',
|
'uploader': 'ARD_Stinkt',
|
||||||
'title': 'German Television does first Edward Snowden Interview (ENGLISH)',
|
'title': 'German Television does first Edward Snowden Interview (ENGLISH)',
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.liveleak.com/view?i=4f7_1392687779',
|
||||||
|
'md5': '42c6d97d54f1db107958760788c5f48f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4f7_1392687779',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'description': "The guy with the cigarette seems amazingly nonchalant about the whole thing... I really hope my friends' reactions would be a bit stronger.\r\n\r\nAction-go to 0:55.",
|
||||||
|
'uploader': 'CapObveus',
|
||||||
|
'title': 'Man is Fatally Struck by Reckless Car While Packing up a Moving Truck',
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
|
|
||||||
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('video_id')
|
video_id = mobj.group('video_id')
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
video_title = self._og_search_title(webpage).replace('LiveLeak.com -', '').strip()
|
||||||
|
video_description = self._og_search_description(webpage)
|
||||||
|
video_uploader = self._html_search_regex(
|
||||||
|
r'By:.*?(\w+)</a>', webpage, 'uploader', fatal=False)
|
||||||
|
age_limit = int_or_none(self._search_regex(
|
||||||
|
r'you confirm that you are ([0-9]+) years and over.',
|
||||||
|
webpage, 'age limit', default=None))
|
||||||
|
|
||||||
sources_raw = self._search_regex(
|
sources_raw = self._search_regex(
|
||||||
r'(?s)sources:\s*(\[.*?\]),', webpage, 'video URLs', default=None)
|
r'(?s)sources:\s*(\[.*?\]),', webpage, 'video URLs', default=None)
|
||||||
if sources_raw is None:
|
if sources_raw is None:
|
||||||
sources_raw = '[{ %s}]' % (
|
alt_source = self._search_regex(
|
||||||
self._search_regex(r'(file: ".*?"),', webpage, 'video URL'))
|
r'(file: ".*?"),', webpage, 'video URL', default=None)
|
||||||
|
if alt_source:
|
||||||
|
sources_raw = '[{ %s}]' % alt_source
|
||||||
|
else:
|
||||||
|
# Maybe an embed?
|
||||||
|
embed_url = self._search_regex(
|
||||||
|
r'<iframe[^>]+src="(http://www.prochan.com/embed\?[^"]+)"',
|
||||||
|
webpage, 'embed URL')
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': embed_url,
|
||||||
|
'id': video_id,
|
||||||
|
'title': video_title,
|
||||||
|
'description': video_description,
|
||||||
|
'uploader': video_uploader,
|
||||||
|
'age_limit': age_limit,
|
||||||
|
}
|
||||||
|
|
||||||
sources_json = re.sub(r'\s([a-z]+):\s', r'"\1": ', sources_raw)
|
sources_json = re.sub(r'\s([a-z]+):\s', r'"\1": ', sources_raw)
|
||||||
sources = json.loads(sources_json)
|
sources = json.loads(sources_json)
|
||||||
@ -49,15 +88,11 @@ class LiveLeakIE(InfoExtractor):
|
|||||||
} for s in sources]
|
} for s in sources]
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
video_title = self._og_search_title(webpage).replace('LiveLeak.com -', '').strip()
|
|
||||||
video_description = self._og_search_description(webpage)
|
|
||||||
video_uploader = self._html_search_regex(
|
|
||||||
r'By:.*?(\w+)</a>', webpage, 'uploader', fatal=False)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': video_title,
|
'title': video_title,
|
||||||
'description': video_description,
|
'description': video_description,
|
||||||
'uploader': video_uploader,
|
'uploader': video_uploader,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
|
'age_limit': age_limit,
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -13,28 +13,28 @@ 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/info/audio51535.html',
|
||||||
'md5': '41ed601768534dd18a9ae34d84798129',
|
'md5': 'bb3cd38e24fbcc866d13b50ca59307b8',
|
||||||
|
'note': 'Audio file',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '191719',
|
'id': '51535',
|
||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
'title': '"Es war schockierend"',
|
'title': 'La Valette entgeht der Hinrichtung',
|
||||||
'description': 'md5:ed7ff8364793545021a6355b97e95f10',
|
'description': 'md5:22f9541913a40fe50091d5cdd7c9f536',
|
||||||
'duration': 112,
|
'duration': 884,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -74,7 +74,8 @@ class NFBIE(InfoExtractor):
|
|||||||
description = media.find('description').text
|
description = media.find('description').text
|
||||||
# It seems assets always go from lower to better quality, so no need to sort
|
# It seems assets always go from lower to better quality, so no need to sort
|
||||||
formats = [{
|
formats = [{
|
||||||
'url': x.find('default/streamerURI').text + '/',
|
'url': x.find('default/streamerURI').text,
|
||||||
|
'app': x.find('default/streamerURI').text.split('/', 3)[3],
|
||||||
'play_path': x.find('default/url').text,
|
'play_path': x.find('default/url').text,
|
||||||
'rtmp_live': False,
|
'rtmp_live': False,
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
|
@ -20,6 +20,7 @@ class SmotriIE(InfoExtractor):
|
|||||||
IE_DESC = 'Smotri.com'
|
IE_DESC = 'Smotri.com'
|
||||||
IE_NAME = 'smotri'
|
IE_NAME = 'smotri'
|
||||||
_VALID_URL = r'^https?://(?:www\.)?(?P<url>smotri\.com/video/view/\?id=(?P<videoid>v(?P<realvideoid>[0-9]+)[a-z0-9]{4}))'
|
_VALID_URL = r'^https?://(?:www\.)?(?P<url>smotri\.com/video/view/\?id=(?P<videoid>v(?P<realvideoid>[0-9]+)[a-z0-9]{4}))'
|
||||||
|
_NETRC_MACHINE = 'smotri'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
# real video id 2610366
|
# real video id 2610366
|
||||||
|
@ -17,6 +17,7 @@ class SohuIE(InfoExtractor):
|
|||||||
u'info_dict': {
|
u'info_dict': {
|
||||||
u'title': u'MV:Far East Movement《The Illest》',
|
u'title': u'MV:Far East Movement《The Illest》',
|
||||||
},
|
},
|
||||||
|
u'skip': u'Only available from China',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
@ -8,14 +10,14 @@ from ..utils import RegexNotFoundError, ExtractorError
|
|||||||
class SpaceIE(InfoExtractor):
|
class SpaceIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:(?:www|m)\.)?space\.com/\d+-(?P<title>[^/\.\?]*?)-video\.html'
|
_VALID_URL = r'https?://(?:(?:www|m)\.)?space\.com/\d+-(?P<title>[^/\.\?]*?)-video\.html'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'add_ie': ['Brightcove'],
|
'add_ie': ['Brightcove'],
|
||||||
u'url': u'http://www.space.com/23373-huge-martian-landforms-detail-revealed-by-european-probe-video.html',
|
'url': 'http://www.space.com/23373-huge-martian-landforms-detail-revealed-by-european-probe-video.html',
|
||||||
u'info_dict': {
|
'info_dict': {
|
||||||
u'id': u'2780937028001',
|
'id': '2780937028001',
|
||||||
u'ext': u'mp4',
|
'ext': 'mp4',
|
||||||
u'title': u'Huge Martian Landforms\' Detail Revealed By European Probe | Video',
|
'title': 'Huge Martian Landforms\' Detail Revealed By European Probe | Video',
|
||||||
u'description': u'md5:db81cf7f3122f95ed234b631a6ea1e61',
|
'description': 'md5:db81cf7f3122f95ed234b631a6ea1e61',
|
||||||
u'uploader': u'TechMedia Networks',
|
'uploader': 'TechMedia Networks',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
67
youtube_dl/extractor/streamcz.py
Normal file
67
youtube_dl/extractor/streamcz.py
Normal 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']),
|
||||||
|
}
|
27
youtube_dl/extractor/syfy.py
Normal file
27
youtube_dl/extractor/syfy.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class SyfyIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://www\.syfy\.com/videos/.+?vid:(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.syfy.com/videos/Robot%20Combat%20League/Behind%20the%20Scenes/vid:2631458',
|
||||||
|
'md5': 'e07de1d52c7278adbb9b9b1c93a66849',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'NmqMrGnXvmO1',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'George Lucas has Advice for his Daughter',
|
||||||
|
'description': 'Listen to what insights George Lucas give his daughter Amanda.',
|
||||||
|
},
|
||||||
|
'add_ie': ['ThePlatform'],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
return self.url_result(self._og_search_video_url(webpage))
|
66
youtube_dl/extractor/testurl.py
Normal file
66
youtube_dl/extractor/testurl.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import ExtractorError
|
||||||
|
|
||||||
|
|
||||||
|
class TestURLIE(InfoExtractor):
|
||||||
|
""" Allows adressing of the test cases as test:yout.*be_1 """
|
||||||
|
|
||||||
|
IE_DESC = False # Do not list
|
||||||
|
_VALID_URL = r'test(?:url)?:(?P<id>(?P<extractor>.+?)(?:_(?P<num>[0-9]+))?)$'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
from ..extractor import gen_extractors
|
||||||
|
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
extractor_id = mobj.group('extractor')
|
||||||
|
all_extractors = gen_extractors()
|
||||||
|
|
||||||
|
rex = re.compile(extractor_id, flags=re.IGNORECASE)
|
||||||
|
matching_extractors = [
|
||||||
|
e for e in all_extractors if rex.search(e.IE_NAME)]
|
||||||
|
|
||||||
|
if len(matching_extractors) == 0:
|
||||||
|
raise ExtractorError(
|
||||||
|
'No extractors matching %r found' % extractor_id,
|
||||||
|
expected=True)
|
||||||
|
elif len(matching_extractors) > 1:
|
||||||
|
# Is it obvious which one to pick?
|
||||||
|
try:
|
||||||
|
extractor = next(
|
||||||
|
ie for ie in matching_extractors
|
||||||
|
if ie.IE_NAME.lower() == extractor_id.lower())
|
||||||
|
except StopIteration:
|
||||||
|
raise ExtractorError(
|
||||||
|
('Found multiple matching extractors: %s' %
|
||||||
|
' '.join(ie.IE_NAME for ie in matching_extractors)),
|
||||||
|
expected=True)
|
||||||
|
|
||||||
|
num_str = mobj.group('num')
|
||||||
|
num = int(num_str) if num_str else 0
|
||||||
|
|
||||||
|
testcases = []
|
||||||
|
t = getattr(extractor, '_TEST', None)
|
||||||
|
if t:
|
||||||
|
testcases.append(t)
|
||||||
|
testcases.extend(getattr(extractor, '_TESTS', []))
|
||||||
|
|
||||||
|
try:
|
||||||
|
tc = testcases[num]
|
||||||
|
except IndexError:
|
||||||
|
raise ExtractorError(
|
||||||
|
('Test case %d not found, got only %d tests' %
|
||||||
|
(num, len(testcases))),
|
||||||
|
expected=True)
|
||||||
|
|
||||||
|
self.to_screen('Test URL: %s' % tc['url'])
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url',
|
||||||
|
'url': tc['url'],
|
||||||
|
'id': video_id,
|
||||||
|
}
|
@ -11,7 +11,10 @@ _x = lambda p: xpath_with_ns(p, {'smil': 'http://www.w3.org/2005/SMIL21/Language
|
|||||||
|
|
||||||
|
|
||||||
class ThePlatformIE(InfoExtractor):
|
class ThePlatformIE(InfoExtractor):
|
||||||
_VALID_URL = r'(?:https?://link\.theplatform\.com/s/[^/]+/|theplatform:)(?P<id>[^/\?]+)'
|
_VALID_URL = r'''(?x)
|
||||||
|
(?:https?://(?:link|player)\.theplatform\.com/[sp]/[^/]+/
|
||||||
|
(?P<config>[^/\?]+/(?:swf|config)/select/)?
|
||||||
|
|theplatform:)(?P<id>[^/\?&]+)'''
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
# from http://www.metacafe.com/watch/cb-e9I_cZgTgIPd/blackberrys_big_bold_z30/
|
# from http://www.metacafe.com/watch/cb-e9I_cZgTgIPd/blackberrys_big_bold_z30/
|
||||||
@ -29,9 +32,7 @@ class ThePlatformIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_info(self, video_id):
|
def _get_info(self, video_id, smil_url):
|
||||||
smil_url = ('http://link.theplatform.com/s/dJ5BDC/{0}/meta.smil?'
|
|
||||||
'format=smil&mbr=true'.format(video_id))
|
|
||||||
meta = self._download_xml(smil_url, video_id)
|
meta = self._download_xml(smil_url, video_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -50,26 +51,34 @@ class ThePlatformIE(InfoExtractor):
|
|||||||
|
|
||||||
head = meta.find(_x('smil:head'))
|
head = meta.find(_x('smil:head'))
|
||||||
body = meta.find(_x('smil:body'))
|
body = meta.find(_x('smil:body'))
|
||||||
base_url = head.find(_x('smil:meta')).attrib['base']
|
|
||||||
switch = body.find(_x('smil:switch'))
|
|
||||||
formats = []
|
|
||||||
for f in switch.findall(_x('smil:video')):
|
|
||||||
attr = f.attrib
|
|
||||||
width = int(attr['width'])
|
|
||||||
height = int(attr['height'])
|
|
||||||
vbr = int(attr['system-bitrate']) // 1000
|
|
||||||
format_id = '%dx%d_%dk' % (width, height, vbr)
|
|
||||||
formats.append({
|
|
||||||
'format_id': format_id,
|
|
||||||
'url': base_url,
|
|
||||||
'play_path': 'mp4:' + attr['src'],
|
|
||||||
'ext': 'flv',
|
|
||||||
'width': width,
|
|
||||||
'height': height,
|
|
||||||
'vbr': vbr,
|
|
||||||
})
|
|
||||||
|
|
||||||
self._sort_formats(formats)
|
f4m_node = body.find(_x('smil:seq/smil:video'))
|
||||||
|
if f4m_node is not None:
|
||||||
|
formats = [{
|
||||||
|
'ext': 'flv',
|
||||||
|
# the parameters are from syfy.com, other sites may use others
|
||||||
|
'url': f4m_node.attrib['src'] + '?g=UXWGVKRWHFSP&hdcore=3.0.3',
|
||||||
|
}]
|
||||||
|
else:
|
||||||
|
base_url = head.find(_x('smil:meta')).attrib['base']
|
||||||
|
switch = body.find(_x('smil:switch'))
|
||||||
|
formats = []
|
||||||
|
for f in switch.findall(_x('smil:video')):
|
||||||
|
attr = f.attrib
|
||||||
|
width = int(attr['width'])
|
||||||
|
height = int(attr['height'])
|
||||||
|
vbr = int(attr['system-bitrate']) // 1000
|
||||||
|
format_id = '%dx%d_%dk' % (width, height, vbr)
|
||||||
|
formats.append({
|
||||||
|
'format_id': format_id,
|
||||||
|
'url': base_url,
|
||||||
|
'play_path': 'mp4:' + attr['src'],
|
||||||
|
'ext': 'flv',
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
'vbr': vbr,
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
@ -83,4 +92,13 @@ class ThePlatformIE(InfoExtractor):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
return self._get_info(video_id)
|
if mobj.group('config'):
|
||||||
|
config_url = url+ '&form=json'
|
||||||
|
config_url = config_url.replace('swf/', 'config/')
|
||||||
|
config_json = self._download_webpage(config_url, video_id, u'Downloading config')
|
||||||
|
config = json.loads(config_json)
|
||||||
|
smil_url = config['releaseUrl'] + '&format=SMIL&formats=MPEG4'
|
||||||
|
else:
|
||||||
|
smil_url = ('http://link.theplatform.com/s/dJ5BDC/{0}/meta.smil?'
|
||||||
|
'format=smil&mbr=true'.format(video_id))
|
||||||
|
return self._get_info(video_id, smil_url)
|
||||||
|
170
youtube_dl/extractor/vesti.py
Normal file
170
youtube_dl/extractor/vesti.py
Normal 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,
|
||||||
|
},
|
||||||
|
'skip': 'Blocked outside Russia'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'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'<iframe.+?src="http://player\.rutv\.ru/iframe/(?P<type>[^/]+)/id/(?P<id>\d+)[^"]*".*?></iframe>', 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,
|
||||||
|
}
|
@ -37,9 +37,10 @@ class VimeoIE(SubtitlesInfoExtractor):
|
|||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://vimeo.com/56015672#at=0',
|
'url': 'http://vimeo.com/56015672#at=0',
|
||||||
'file': '56015672.mp4',
|
|
||||||
'md5': '8879b6cc097e987f02484baf890129e5',
|
'md5': '8879b6cc097e987f02484baf890129e5',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '56015672',
|
||||||
|
'ext': 'mp4',
|
||||||
"upload_date": "20121220",
|
"upload_date": "20121220",
|
||||||
"description": "This is a test case for youtube-dl.\nFor more information, see github.com/rg3/youtube-dl\nTest chars: \u2605 \" ' \u5e78 / \\ \u00e4 \u21ad \U0001d550",
|
"description": "This is a test case for youtube-dl.\nFor more information, see github.com/rg3/youtube-dl\nTest chars: \u2605 \" ' \u5e78 / \\ \u00e4 \u21ad \U0001d550",
|
||||||
"uploader_id": "user7108434",
|
"uploader_id": "user7108434",
|
||||||
|
@ -6,6 +6,9 @@ import json
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
compat_urllib_request,
|
||||||
|
compat_urllib_parse,
|
||||||
compat_str,
|
compat_str,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
)
|
)
|
||||||
@ -14,31 +17,80 @@ from ..utils import (
|
|||||||
class VKIE(InfoExtractor):
|
class VKIE(InfoExtractor):
|
||||||
IE_NAME = 'vk.com'
|
IE_NAME = 'vk.com'
|
||||||
_VALID_URL = r'https?://vk\.com/(?:videos.*?\?.*?z=)?video(?P<id>.*?)(?:\?|%2F|$)'
|
_VALID_URL = r'https?://vk\.com/(?:videos.*?\?.*?z=)?video(?P<id>.*?)(?:\?|%2F|$)'
|
||||||
|
_NETRC_MACHINE = 'vk'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [
|
||||||
'url': 'http://vk.com/videos-77521?z=video-77521_162222515%2Fclub77521',
|
{
|
||||||
'file': '162222515.flv',
|
'url': 'http://vk.com/videos-77521?z=video-77521_162222515%2Fclub77521',
|
||||||
'md5': '0deae91935c54e00003c2a00646315f0',
|
'md5': '0deae91935c54e00003c2a00646315f0',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'title': 'ProtivoGunz - Хуёвая песня',
|
'id': '162222515',
|
||||||
'uploader': 'Noize MC',
|
'ext': 'flv',
|
||||||
|
'title': 'ProtivoGunz - Хуёвая песня',
|
||||||
|
'uploader': 'Noize MC',
|
||||||
|
'duration': 195,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
'url': 'http://vk.com/video4643923_163339118',
|
||||||
'url': 'http://vk.com/video4643923_163339118',
|
'md5': 'f79bccb5cd182b1f43502ca5685b2b36',
|
||||||
'file': '163339118.mp4',
|
'info_dict': {
|
||||||
'md5': 'f79bccb5cd182b1f43502ca5685b2b36',
|
'id': '163339118',
|
||||||
'info_dict': {
|
'ext': 'mp4',
|
||||||
'uploader': 'Elvira Dzhonik',
|
'uploader': 'Elvira Dzhonik',
|
||||||
'title': 'Dream Theater - Hollow Years Live at Budokan 720*',
|
'title': 'Dream Theater - Hollow Years Live at Budokan 720*',
|
||||||
|
'duration': 558,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://vk.com/video-8871596_164049491',
|
||||||
|
'md5': 'a590bcaf3d543576c9bd162812387666',
|
||||||
|
'note': 'Only available for registered users',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '164049491',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'uploader': 'Триллеры',
|
||||||
|
'title': '► Бойцовский клуб / Fight Club 1999 [HD 720]\u00a0',
|
||||||
|
'duration': 8352,
|
||||||
|
},
|
||||||
|
'skip': 'Requires vk account credentials',
|
||||||
}
|
}
|
||||||
}]
|
]
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
(username, password) = self._get_login_info()
|
||||||
|
if username is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
login_form = {
|
||||||
|
'act': 'login',
|
||||||
|
'role': 'al_frame',
|
||||||
|
'expire': '1',
|
||||||
|
'email': username,
|
||||||
|
'pass': password,
|
||||||
|
}
|
||||||
|
|
||||||
|
request = compat_urllib_request.Request('https://login.vk.com/?act=login',
|
||||||
|
compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
||||||
|
login_page = self._download_webpage(request, None, note='Logging in as %s' % username)
|
||||||
|
|
||||||
|
if re.search(r'onLoginFailed', login_page):
|
||||||
|
raise ExtractorError('Unable to login, incorrect username and/or password', expected=True)
|
||||||
|
|
||||||
|
def _real_initialize(self):
|
||||||
|
self._login()
|
||||||
|
|
||||||
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('id')
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
info_url = 'http://vk.com/al_video.php?act=show&al=1&video=%s' % video_id
|
info_url = 'http://vk.com/al_video.php?act=show&al=1&video=%s' % video_id
|
||||||
info_page = self._download_webpage(info_url, video_id)
|
info_page = self._download_webpage(info_url, video_id)
|
||||||
|
|
||||||
|
if re.search(r'<!>Please log in or <', info_page):
|
||||||
|
raise ExtractorError('This video is only available for registered users, '
|
||||||
|
'use --username and --password options to provide account credentials.', expected=True)
|
||||||
|
|
||||||
m_yt = re.search(r'src="(http://www.youtube.com/.*?)"', info_page)
|
m_yt = re.search(r'src="(http://www.youtube.com/.*?)"', info_page)
|
||||||
if m_yt is not None:
|
if m_yt is not None:
|
||||||
self.to_screen(u'Youtube video detected')
|
self.to_screen(u'Youtube video detected')
|
||||||
@ -60,4 +112,5 @@ class VKIE(InfoExtractor):
|
|||||||
'title': unescapeHTML(data['md_title']),
|
'title': unescapeHTML(data['md_title']),
|
||||||
'thumbnail': data.get('jpg'),
|
'thumbnail': data.get('jpg'),
|
||||||
'uploader': data.get('md_author'),
|
'uploader': data.get('md_author'),
|
||||||
|
'duration': data.get('duration')
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
|
@ -138,13 +138,14 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
(?:(?:(?:(?:\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/|
|
||||||
|
(?:www\.)?yourepeat\.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
|
||||||
(?: # the various things that can precede the ID:
|
(?: # the various things that can precede the ID:
|
||||||
(?:(?:v|embed|e)/) # v/ or embed/ or e/
|
(?:(?:v|embed|e)/) # v/ or embed/ or e/
|
||||||
|(?: # or the v= param in all its forms
|
|(?: # or the v= param in all its forms
|
||||||
(?:(?:watch|movie)(?:_popup)?(?:\.php)?)? # preceding watch(_popup|.php) or nothing (like /?v=xxxx)
|
(?:(?:watch|movie)(?:_popup)?(?:\.php)?/?)? # preceding watch(_popup|.php) or nothing (like /?v=xxxx)
|
||||||
(?:\?|\#!?) # the params delimiter ? or # or #!
|
(?:\?|\#!?) # the params delimiter ? or # or #!
|
||||||
(?:.*?&)? # any other preceding param (like /?s=tuff&v=xxxx)
|
(?:.*?&)? # any other preceding param (like /?s=tuff&v=xxxx)
|
||||||
v=
|
v=
|
||||||
@ -1694,7 +1695,8 @@ class YoutubeSearchIE(SearchInfoExtractor):
|
|||||||
api_response = data['data']
|
api_response = data['data']
|
||||||
|
|
||||||
if 'items' not in api_response:
|
if 'items' not in api_response:
|
||||||
raise ExtractorError(u'[youtube] No video results')
|
raise ExtractorError(
|
||||||
|
u'[youtube] No video results', expected=True)
|
||||||
|
|
||||||
new_ids = list(video['id'] for video in api_response['items'])
|
new_ids = list(video['id'] for video in api_response['items'])
|
||||||
video_ids += new_ids
|
video_ids += new_ids
|
||||||
@ -1814,7 +1816,7 @@ class YoutubeTruncatedURLIE(InfoExtractor):
|
|||||||
IE_NAME = 'youtube:truncated_url'
|
IE_NAME = 'youtube:truncated_url'
|
||||||
IE_DESC = False # Do not list
|
IE_DESC = False # Do not list
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
(?:https?://)?[^/]+/watch\?feature=[a-z_]+$|
|
(?:https?://)?[^/]+/watch\?(?:feature=[a-z_]+)?$|
|
||||||
(?:https?://)?(?:www\.)?youtube\.com/attribution_link\?a=[^&]+$
|
(?:https?://)?(?:www\.)?youtube\.com/attribution_link\?a=[^&]+$
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import platform
|
|||||||
import re
|
import re
|
||||||
import ssl
|
import ssl
|
||||||
import socket
|
import socket
|
||||||
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
@ -761,6 +762,7 @@ def unified_strdate(date_str):
|
|||||||
date_str = re.sub(r' ?(\+|-)[0-9]{2}:?[0-9]{2}$', '', date_str)
|
date_str = re.sub(r' ?(\+|-)[0-9]{2}:?[0-9]{2}$', '', date_str)
|
||||||
format_expressions = [
|
format_expressions = [
|
||||||
'%d %B %Y',
|
'%d %B %Y',
|
||||||
|
'%d %b %Y',
|
||||||
'%B %d %Y',
|
'%B %d %Y',
|
||||||
'%b %d %Y',
|
'%b %d %Y',
|
||||||
'%Y-%m-%d',
|
'%Y-%m-%d',
|
||||||
@ -1143,7 +1145,7 @@ def parse_duration(s):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
m = re.match(
|
m = re.match(
|
||||||
r'(?:(?:(?P<hours>[0-9]+):)?(?P<mins>[0-9]+):)?(?P<secs>[0-9]+)$', s)
|
r'(?:(?:(?P<hours>[0-9]+)[:h])?(?P<mins>[0-9]+)[:m])?(?P<secs>[0-9]+)s?$', s)
|
||||||
if not m:
|
if not m:
|
||||||
return None
|
return None
|
||||||
res = int(m.group('secs'))
|
res = int(m.group('secs'))
|
||||||
@ -1220,3 +1222,20 @@ def uppercase_escape(s):
|
|||||||
return re.sub(
|
return re.sub(
|
||||||
r'\\U([0-9a-fA-F]{8})',
|
r'\\U([0-9a-fA-F]{8})',
|
||||||
lambda m: compat_chr(int(m.group(1), base=16)), s)
|
lambda m: compat_chr(int(m.group(1), base=16)), s)
|
||||||
|
|
||||||
|
try:
|
||||||
|
struct.pack(u'!I', 0)
|
||||||
|
except TypeError:
|
||||||
|
# In Python 2.6 (and some 2.7 versions), struct requires a bytes argument
|
||||||
|
def struct_pack(spec, *args):
|
||||||
|
if isinstance(spec, compat_str):
|
||||||
|
spec = spec.encode('ascii')
|
||||||
|
return struct.pack(spec, *args)
|
||||||
|
|
||||||
|
def struct_unpack(spec, *args):
|
||||||
|
if isinstance(spec, compat_str):
|
||||||
|
spec = spec.encode('ascii')
|
||||||
|
return struct.unpack(spec, *args)
|
||||||
|
else:
|
||||||
|
struct_pack = struct.pack
|
||||||
|
struct_unpack = struct.unpack
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
|
|
||||||
__version__ = '2014.02.10'
|
__version__ = '2014.02.19.1'
|
||||||
|
Reference in New Issue
Block a user