mirror of
https://github.com/ytdl-org/youtube-dl.git
synced 2025-08-03 02:50:01 -05:00
Compare commits
40 Commits
2015.01.03
...
2015.01.05
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f4bca0b348 | ||
![]() |
6291438073 | ||
![]() |
18c3c15391 | ||
![]() |
dda620e88c | ||
![]() |
d7cc31b63e | ||
![]() |
5e3e1c82d8 | ||
![]() |
aa80652f47 | ||
![]() |
9d247bbd2d | ||
![]() |
93e40a7b2f | ||
![]() |
03ff2cc1c4 | ||
![]() |
a285b6377b | ||
![]() |
cd791a5ea0 | ||
![]() |
87830900a9 | ||
![]() |
dfc9d9f50a | ||
![]() |
75311a7e16 | ||
![]() |
628bc4d1e7 | ||
![]() |
a4c3f48639 | ||
![]() |
bdf80aa542 | ||
![]() |
adf3c58ad3 | ||
![]() |
caf90bfaa5 | ||
![]() |
2f985f4bb4 | ||
![]() |
67c2bcdf4c | ||
![]() |
1d2d0e3ff2 | ||
![]() |
9fda6ee39f | ||
![]() |
bc3e582fe4 | ||
![]() |
bc1fc5ddbc | ||
![]() |
63948fc62c | ||
![]() |
f4858a7103 | ||
![]() |
26886e6140 | ||
![]() |
7a1818c99b | ||
![]() |
2ccd1b10e5 | ||
![]() |
788fa208c8 | ||
![]() |
8848314c08 | ||
![]() |
c11125f9ed | ||
![]() |
95ceeec722 | ||
![]() |
b68ff25917 | ||
![]() |
3e3327ea17 | ||
![]() |
b158bb8693 | ||
![]() |
2bf098eda4 | ||
![]() |
382e05fa56 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -31,3 +31,5 @@ updates_key.pem
|
||||
test/testdata
|
||||
.tox
|
||||
youtube-dl.zsh
|
||||
.idea
|
||||
.idea/*
|
@@ -9,7 +9,6 @@ notifications:
|
||||
email:
|
||||
- filippo.valsorda@gmail.com
|
||||
- phihag@phihag.de
|
||||
- jaime.marquinez.ferrandiz+travis@gmail.com
|
||||
- yasoob.khld@gmail.com
|
||||
# irc:
|
||||
# channels:
|
||||
|
2
AUTHORS
2
AUTHORS
@@ -98,3 +98,5 @@ Will Glynn
|
||||
Max Reimann
|
||||
Cédric Luthi
|
||||
Thijs Vermeir
|
||||
Joel Leclerc
|
||||
Christopher Krooss
|
||||
|
2
Makefile
2
Makefile
@@ -46,7 +46,7 @@ test:
|
||||
ot: offlinetest
|
||||
|
||||
offlinetest: codetest
|
||||
nosetests --verbose test --exclude test_download --exclude test_age_restriction --exclude test_subtitles --exclude test_write_annotations
|
||||
nosetests --verbose test --exclude test_download --exclude test_age_restriction --exclude test_subtitles --exclude test_write_annotations --exclude test_youtube_lists
|
||||
|
||||
tar: youtube-dl.tar.gz
|
||||
|
||||
|
@@ -218,7 +218,7 @@ class TestFormatSelection(unittest.TestCase):
|
||||
# 3D
|
||||
'85', '84', '102', '83', '101', '82', '100',
|
||||
# Dash video
|
||||
'138', '137', '248', '136', '247', '135', '246',
|
||||
'137', '248', '136', '247', '135', '246',
|
||||
'245', '244', '134', '243', '133', '242', '160',
|
||||
# Dash audio
|
||||
'141', '172', '140', '171', '139',
|
||||
|
@@ -11,7 +11,6 @@ from ..compat import (
|
||||
compat_urllib_request,
|
||||
)
|
||||
from ..utils import (
|
||||
check_executable,
|
||||
encodeFilename,
|
||||
)
|
||||
|
||||
@@ -27,16 +26,13 @@ class HlsFD(FileDownloader):
|
||||
'-bsf:a', 'aac_adtstoasc',
|
||||
encodeFilename(tmpfilename, for_subprocess=True)]
|
||||
|
||||
for program in ['avconv', 'ffmpeg']:
|
||||
if check_executable(program, ['-version']):
|
||||
break
|
||||
else:
|
||||
ffpp = FFmpegPostProcessor(downloader=self)
|
||||
program = ffpp._executable
|
||||
if program is None:
|
||||
self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
|
||||
return False
|
||||
cmd = [program] + args
|
||||
|
||||
ffpp = FFmpegPostProcessor(downloader=self)
|
||||
ffpp.check_version()
|
||||
cmd = [program] + args
|
||||
|
||||
retval = subprocess.call(cmd)
|
||||
if retval == 0:
|
||||
|
@@ -4,7 +4,6 @@ import os
|
||||
import subprocess
|
||||
|
||||
from .common import FileDownloader
|
||||
from ..compat import compat_subprocess_get_DEVNULL
|
||||
from ..utils import (
|
||||
check_executable,
|
||||
encodeFilename,
|
||||
|
@@ -325,6 +325,7 @@ from .prosiebensat1 import ProSiebenSat1IE
|
||||
from .pyvideo import PyvideoIE
|
||||
from .quickvid import QuickVidIE
|
||||
from .radiode import RadioDeIE
|
||||
from .radiobremen import RadioBremenIE
|
||||
from .radiofrance import RadioFranceIE
|
||||
from .rai import RaiIE
|
||||
from .rbmaradio import RBMARadioIE
|
||||
@@ -345,6 +346,7 @@ from .ruhd import RUHDIE
|
||||
from .rutube import (
|
||||
RutubeIE,
|
||||
RutubeChannelIE,
|
||||
RutubeEmbedIE,
|
||||
RutubeMovieIE,
|
||||
RutubePersonIE,
|
||||
)
|
||||
@@ -510,6 +512,7 @@ from .wdr import (
|
||||
WDRMobileIE,
|
||||
WDRMausIE,
|
||||
)
|
||||
from .webofstories import WebOfStoriesIE
|
||||
from .weibo import WeiboIE
|
||||
from .wimp import WimpIE
|
||||
from .wistia import WistiaIE
|
||||
@@ -545,7 +548,6 @@ from .youtube import (
|
||||
YoutubeSearchURLIE,
|
||||
YoutubeShowIE,
|
||||
YoutubeSubscriptionsIE,
|
||||
YoutubeTopListIE,
|
||||
YoutubeTruncatedIDIE,
|
||||
YoutubeTruncatedURLIE,
|
||||
YoutubeUserIE,
|
||||
|
@@ -7,6 +7,7 @@ from ..compat import compat_urllib_parse
|
||||
from ..utils import (
|
||||
determine_ext,
|
||||
ExtractorError,
|
||||
remove_end,
|
||||
)
|
||||
|
||||
|
||||
@@ -27,23 +28,18 @@ class AUEngineIE(InfoExtractor):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
title = self._html_search_regex(r'<title>(?P<title>.+?)</title>', webpage, 'title')
|
||||
title = title.strip()
|
||||
links = re.findall(r'\s(?:file|url):\s*["\']([^\'"]+)["\']', webpage)
|
||||
links = map(compat_urllib_parse.unquote, links)
|
||||
title = self._html_search_regex(
|
||||
r'<title>\s*(?P<title>.+?)\s*</title>', webpage, 'title')
|
||||
video_urls = re.findall(r'http://\w+.auengine.com/vod/.*[^\W]', webpage)
|
||||
video_url = compat_urllib_parse.unquote(video_urls[0])
|
||||
thumbnails = re.findall(r'http://\w+.auengine.com/thumb/.*[^\W]', webpage)
|
||||
thumbnail = compat_urllib_parse.unquote(thumbnails[0])
|
||||
|
||||
thumbnail = None
|
||||
video_url = None
|
||||
for link in links:
|
||||
if link.endswith('.png'):
|
||||
thumbnail = link
|
||||
elif '/videos/' in link:
|
||||
video_url = link
|
||||
if not video_url:
|
||||
raise ExtractorError('Could not find video URL')
|
||||
|
||||
ext = '.' + determine_ext(video_url)
|
||||
if ext == title[-len(ext):]:
|
||||
title = title[:-len(ext)]
|
||||
title = remove_end(title, ext)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
|
@@ -131,12 +131,13 @@ class GenericIE(InfoExtractor):
|
||||
# ooyala video
|
||||
{
|
||||
'url': 'http://www.rollingstone.com/music/videos/norwegian-dj-cashmere-cat-goes-spartan-on-with-me-premiere-20131219',
|
||||
'md5': '5644c6ca5d5782c1d0d350dad9bd840c',
|
||||
'md5': '166dd577b433b4d4ebfee10b0824d8ff',
|
||||
'info_dict': {
|
||||
'id': 'BwY2RxaTrTkslxOfcan0UCf0YqyvWysJ',
|
||||
'ext': 'mp4',
|
||||
'title': '2cc213299525360.mov', # that's what we get
|
||||
},
|
||||
'add_ie': ['Ooyala'],
|
||||
},
|
||||
# google redirect
|
||||
{
|
||||
@@ -146,7 +147,7 @@ class GenericIE(InfoExtractor):
|
||||
'ext': 'mp4',
|
||||
'upload_date': '20130224',
|
||||
'uploader_id': 'TheVerge',
|
||||
'description': 'Chris Ziegler takes a look at the Alcatel OneTouch Fire and the ZTE Open; two of the first Firefox OS handsets to be officially announced.',
|
||||
'description': 're:^Chris Ziegler takes a look at the\.*',
|
||||
'uploader': 'The Verge',
|
||||
'title': 'First Firefox OS phones side-by-side',
|
||||
},
|
||||
@@ -925,7 +926,7 @@ class GenericIE(InfoExtractor):
|
||||
|
||||
# Look for embedded TED player
|
||||
mobj = re.search(
|
||||
r'<iframe[^>]+?src=(["\'])(?P<url>http://embed\.ted\.com/.+?)\1', webpage)
|
||||
r'<iframe[^>]+?src=(["\'])(?P<url>https?://embed(?:-ssl)?\.ted\.com/.+?)\1', webpage)
|
||||
if mobj is not None:
|
||||
return self.url_result(mobj.group('url'), 'TED')
|
||||
|
||||
|
@@ -22,8 +22,10 @@ class KhanAcademyIE(InfoExtractor):
|
||||
'description': 'The perfect cipher',
|
||||
'duration': 176,
|
||||
'uploader': 'Brit Cruise',
|
||||
'uploader_id': 'khanacademy',
|
||||
'upload_date': '20120411',
|
||||
}
|
||||
},
|
||||
'add_ie': ['Youtube'],
|
||||
}, {
|
||||
'url': 'https://www.khanacademy.org/math/applied-math/cryptography',
|
||||
'info_dict': {
|
||||
|
@@ -2,7 +2,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import json
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
@@ -28,7 +27,6 @@ class LRTIE(InfoExtractor):
|
||||
'params': {
|
||||
'skip_download': True, # HLS download
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
@@ -44,7 +42,9 @@ class LRTIE(InfoExtractor):
|
||||
|
||||
formats = []
|
||||
for js in re.findall(r'(?s)config:\s*(\{.*?\})', webpage):
|
||||
data = json.loads(js_to_json(js))
|
||||
data = self._parse_json(js, video_id, transform_source=js_to_json)
|
||||
if 'provider' not in data:
|
||||
continue
|
||||
if data['provider'] == 'rtmp':
|
||||
formats.append({
|
||||
'format_id': 'rtmp',
|
||||
|
@@ -22,7 +22,11 @@ class NormalbootsIE(InfoExtractor):
|
||||
'description': 'Jon is late for Christmas. Typical. Thanks to: Paul Ritchey for Co-Writing/Filming: http://www.youtube.com/user/ContinueShow Michael Azzi for Christmas Intro Animation: http://michafrar.tumblr.com/ Jerrod Waters for Christmas Intro Music: http://www.youtube.com/user/xXJerryTerryXx Casey Ormond for ‘Tense Battle Theme’:\xa0http://www.youtube.com/Kiamet/',
|
||||
'uploader': 'JonTron',
|
||||
'upload_date': '20140125',
|
||||
}
|
||||
},
|
||||
'params': {
|
||||
# rtmp download
|
||||
'skip_download': True,
|
||||
},
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
63
youtube_dl/extractor/radiobremen.py
Normal file
63
youtube_dl/extractor/radiobremen.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import parse_duration
|
||||
|
||||
|
||||
class RadioBremenIE(InfoExtractor):
|
||||
_VALID_URL = r'http?://(?:www\.)?radiobremen\.de/mediathek/(?:index\.html)?\?id=(?P<id>[0-9]+)'
|
||||
IE_NAME = 'radiobremen'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://www.radiobremen.de/mediathek/index.html?id=114720',
|
||||
'info_dict': {
|
||||
'id': '114720',
|
||||
'ext': 'mp4',
|
||||
'duration': 1685,
|
||||
'width': 512,
|
||||
'title': 'buten un binnen vom 22. Dezember',
|
||||
'thumbnail': 're:https?://.*\.jpg$',
|
||||
'description': 'Unter anderem mit diesen Themen: 45 Flüchtlinge sind in Worpswede angekommen +++ Freies Internet für alle: Bremer arbeiten an einem flächendeckenden W-Lan-Netzwerk +++ Aktivisten kämpfen für das Unibad +++ So war das Wetter 2014 +++',
|
||||
},
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
meta_url = "http://www.radiobremen.de/apps/php/mediathek/metadaten.php?id=%s" % video_id
|
||||
meta_doc = self._download_webpage(
|
||||
meta_url, video_id, 'Downloading metadata')
|
||||
title = self._html_search_regex(
|
||||
r"<h1.*>(?P<title>.+)</h1>", meta_doc, "title")
|
||||
description = self._html_search_regex(
|
||||
r"<p>(?P<description>.*)</p>", meta_doc, "description", fatal=False)
|
||||
duration = parse_duration(self._html_search_regex(
|
||||
r"Länge:</td>\s+<td>(?P<duration>[0-9]+:[0-9]+)</td>",
|
||||
meta_doc, "duration", fatal=False))
|
||||
|
||||
page_doc = self._download_webpage(
|
||||
url, video_id, 'Downloading video information')
|
||||
mobj = re.search(
|
||||
r"ardformatplayerclassic\(\'playerbereich\',\'(?P<width>[0-9]+)\',\'.*\',\'(?P<video_id>[0-9]+)\',\'(?P<secret>[0-9]+)\',\'(?P<thumbnail>.+)\',\'\'\)",
|
||||
page_doc)
|
||||
video_url = (
|
||||
"http://dl-ondemand.radiobremen.de/mediabase/%s/%s_%s_%s.mp4" %
|
||||
(video_id, video_id, mobj.group("secret"), mobj.group('width')))
|
||||
|
||||
formats = [{
|
||||
'url': video_url,
|
||||
'ext': 'mp4',
|
||||
'width': int(mobj.group("width")),
|
||||
}]
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'duration': duration,
|
||||
'formats': formats,
|
||||
'thumbnail': mobj.group('thumbnail'),
|
||||
}
|
@@ -8,7 +8,7 @@ from ..utils import parse_duration
|
||||
|
||||
class RtlXlIE(InfoExtractor):
|
||||
IE_NAME = 'rtlxl.nl'
|
||||
_VALID_URL = r'https?://www\.rtlxl\.nl/#!/[^/]+/(?P<uuid>[^/?]+)'
|
||||
_VALID_URL = r'https?://(www\.)?rtlxl\.nl/#!/[^/]+/(?P<uuid>[^/?]+)'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://www.rtlxl.nl/#!/rtl-nieuws-132237/6e4203a6-0a5e-3596-8424-c599a59e0677',
|
||||
|
@@ -70,6 +70,37 @@ class RutubeIE(InfoExtractor):
|
||||
}
|
||||
|
||||
|
||||
class RutubeEmbedIE(InfoExtractor):
|
||||
IE_NAME = 'rutube:embed'
|
||||
IE_DESC = 'Rutube embedded videos'
|
||||
_VALID_URL = 'https?://rutube\.ru/video/embed/(?P<id>[0-9]+)'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://rutube.ru/video/embed/6722881?vk_puid37=&vk_puid38=',
|
||||
'info_dict': {
|
||||
'id': 'a10e53b86e8f349080f718582ce4c661',
|
||||
'ext': 'mp4',
|
||||
'upload_date': '20131223',
|
||||
'uploader_id': '297833',
|
||||
'description': 'Видео группы ★http://vk.com/foxkidsreset★ музей Fox Kids и Jetix<br/><br/> восстановлено и сделано в шикоформате subziro89 http://vk.com/subziro89',
|
||||
'uploader': 'subziro89 ILya',
|
||||
'title': 'Мистический городок Эйри в Индиан 5 серия озвучка subziro89',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': 'Requires ffmpeg',
|
||||
},
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
embed_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, embed_id)
|
||||
|
||||
canonical_url = self._html_search_regex(
|
||||
r'<link\s+rel="canonical"\s+href="([^"]+?)"', webpage,
|
||||
'Canonical URL')
|
||||
return self.url_result(canonical_url, 'Rutube')
|
||||
|
||||
|
||||
class RutubeChannelIE(InfoExtractor):
|
||||
IE_NAME = 'rutube:channel'
|
||||
IE_DESC = 'Rutube channels'
|
||||
|
80
youtube_dl/extractor/soulanime.py
Normal file
80
youtube_dl/extractor/soulanime.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
HEADRequest,
|
||||
urlhandle_detect_ext,
|
||||
)
|
||||
|
||||
|
||||
class SoulAnimeWatchingIE(InfoExtractor):
|
||||
IE_NAME = "soulanime:watching"
|
||||
IE_DESC = "SoulAnime video"
|
||||
_TEST = {
|
||||
'url': 'http://www.soul-anime.net/watching/seirei-tsukai-no-blade-dance-episode-9/',
|
||||
'md5': '05fae04abf72298098b528e98abf4298',
|
||||
'info_dict': {
|
||||
'id': 'seirei-tsukai-no-blade-dance-episode-9',
|
||||
'ext': 'mp4',
|
||||
'title': 'seirei-tsukai-no-blade-dance-episode-9',
|
||||
'description': 'seirei-tsukai-no-blade-dance-episode-9'
|
||||
}
|
||||
}
|
||||
_VALID_URL = r'http://[w.]*soul-anime\.(?P<domain>[^/]+)/watch[^/]*/(?P<id>[^/]+)'
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
domain = mobj.group('domain')
|
||||
|
||||
page = self._download_webpage(url, video_id)
|
||||
|
||||
video_url_encoded = self._html_search_regex(
|
||||
r'<div id="download">[^<]*<a href="(?P<url>[^"]+)"', page, 'url')
|
||||
video_url = "http://www.soul-anime." + domain + video_url_encoded
|
||||
|
||||
ext_req = HEADRequest(video_url)
|
||||
ext_handle = self._request_webpage(
|
||||
ext_req, video_id, note='Determining extension')
|
||||
ext = urlhandle_detect_ext(ext_handle)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'url': video_url,
|
||||
'ext': ext,
|
||||
'title': video_id,
|
||||
'description': video_id
|
||||
}
|
||||
|
||||
|
||||
class SoulAnimeSeriesIE(InfoExtractor):
|
||||
IE_NAME = "soulanime:series"
|
||||
IE_DESC = "SoulAnime Series"
|
||||
|
||||
_VALID_URL = r'http://[w.]*soul-anime\.(?P<domain>[^/]+)/anime./(?P<id>[^/]+)'
|
||||
|
||||
_EPISODE_REGEX = r'<option value="(/watch[^/]*/[^"]+)">[^<]*</option>'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://www.soul-anime.net/anime1/black-rock-shooter-tv/',
|
||||
'info_dict': {
|
||||
'id': 'black-rock-shooter-tv'
|
||||
},
|
||||
'playlist_count': 8
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
series_id = mobj.group('id')
|
||||
domain = mobj.group('domain')
|
||||
|
||||
pattern = re.compile(self._EPISODE_REGEX)
|
||||
|
||||
page = self._download_webpage(url, series_id, "Downloading series page")
|
||||
mobj = pattern.findall(page)
|
||||
|
||||
entries = [self.url_result("http://www.soul-anime." + domain + obj) for obj in mobj]
|
||||
|
||||
return self.playlist_result(entries, series_id)
|
@@ -13,7 +13,7 @@ from ..compat import (
|
||||
class TEDIE(SubtitlesInfoExtractor):
|
||||
_VALID_URL = r'''(?x)
|
||||
(?P<proto>https?://)
|
||||
(?P<type>www|embed)(?P<urlmain>\.ted\.com/
|
||||
(?P<type>www|embed(?:-ssl)?)(?P<urlmain>\.ted\.com/
|
||||
(
|
||||
(?P<type_playlist>playlists(?:/\d+)?) # We have a playlist
|
||||
|
|
||||
@@ -98,7 +98,7 @@ class TEDIE(SubtitlesInfoExtractor):
|
||||
|
||||
def _real_extract(self, url):
|
||||
m = re.match(self._VALID_URL, url, re.VERBOSE)
|
||||
if m.group('type') == 'embed':
|
||||
if m.group('type').startswith('embed'):
|
||||
desktop_url = m.group('proto') + 'www' + m.group('urlmain')
|
||||
return self.url_result(desktop_url, 'TED')
|
||||
name = m.group('name')
|
||||
|
@@ -1,15 +1,13 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
|
||||
|
||||
class TF1IE(InfoExtractor):
|
||||
"""TF1 uses the wat.tv player."""
|
||||
_VALID_URL = r'http://videos\.tf1\.fr/.*-(?P<id>.*?)\.html'
|
||||
_TEST = {
|
||||
_VALID_URL = r'http://(?:videos\.tf1|www\.tfou)\.fr/.*?-(?P<id>\d+)(?:-\d+)?\.html'
|
||||
_TESTS = {
|
||||
'url': 'http://videos.tf1.fr/auto-moto/citroen-grand-c4-picasso-2013-presentation-officielle-8062060.html',
|
||||
'info_dict': {
|
||||
'id': '10635995',
|
||||
@@ -21,14 +19,26 @@ class TF1IE(InfoExtractor):
|
||||
# Sometimes wat serves the whole file with the --test option
|
||||
'skip_download': True,
|
||||
},
|
||||
}, {
|
||||
'url': 'http://www.tfou.fr/chuggington/videos/le-grand-mysterioso-chuggington-7085291-739.html',
|
||||
'info_dict': {
|
||||
'id': '12043945',
|
||||
'ext': 'mp4',
|
||||
'title': 'Le grand Mystérioso - Chuggington',
|
||||
'description': 'Le grand Mystérioso - Emery rêve qu\'un article lui soit consacré dans le journal.',
|
||||
'upload_date': '20150103',
|
||||
},
|
||||
'params': {
|
||||
# Sometimes wat serves the whole file with the --test option
|
||||
'skip_download': True,
|
||||
},
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
video_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
embed_url = self._html_search_regex(
|
||||
r'"(https://www.wat.tv/embedframe/.*?)"', webpage, 'embed url')
|
||||
r'["\'](https?://www.wat.tv/embedframe/.*?)["\']', webpage, 'embed url')
|
||||
embed_page = self._download_webpage(embed_url, video_id,
|
||||
'Downloading embed player page')
|
||||
wat_id = self._search_regex(r'UVID=(.*?)&', embed_page, 'wat id')
|
||||
|
@@ -14,28 +14,17 @@ class VimpleIE(InfoExtractor):
|
||||
IE_DESC = 'Vimple.ru'
|
||||
_VALID_URL = r'https?://(player.vimple.ru/iframe|vimple.ru)/(?P<id>[a-f0-9]{10,})'
|
||||
_TESTS = [
|
||||
# Quality: Large, from iframe
|
||||
{
|
||||
'url': 'http://player.vimple.ru/iframe/b132bdfd71b546d3972f9ab9a25f201c',
|
||||
'url': 'http://vimple.ru/c0f6b1687dcd4000a97ebe70068039cf',
|
||||
'md5': '2e750a330ed211d3fd41821c6ad9a279',
|
||||
'info_dict': {
|
||||
'id': 'b132bdfd71b546d3972f9ab9a25f201c',
|
||||
'title': 'great-escape-minecraft.flv',
|
||||
'id': 'c0f6b1687dcd4000a97ebe70068039cf',
|
||||
'ext': 'mp4',
|
||||
'duration': 352,
|
||||
'webpage_url': 'http://vimple.ru/b132bdfd71b546d3972f9ab9a25f201c',
|
||||
'title': 'Sunset',
|
||||
'duration': 20,
|
||||
'thumbnail': 're:https?://.*?\.jpg',
|
||||
},
|
||||
},
|
||||
# Quality: Medium, from mainpage
|
||||
{
|
||||
'url': 'http://vimple.ru/a15950562888453b8e6f9572dc8600cd',
|
||||
'info_dict': {
|
||||
'id': 'a15950562888453b8e6f9572dc8600cd',
|
||||
'title': 'DB 01',
|
||||
'ext': 'flv',
|
||||
'duration': 1484,
|
||||
'webpage_url': 'http://vimple.ru/a15950562888453b8e6f9572dc8600cd',
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
@@ -164,6 +164,15 @@ class VKIE(InfoExtractor):
|
||||
self.to_screen('Youtube video detected')
|
||||
return self.url_result(m_yt.group(1), 'Youtube')
|
||||
|
||||
m_rutube = re.search(
|
||||
r'\ssrc="((?:https?:)?//rutube\.ru\\?/video\\?/embed(?:.*?))\\?"', info_page)
|
||||
assert m_rutube
|
||||
if m_rutube is not None:
|
||||
self.to_screen('rutube video detected')
|
||||
rutube_url = self._proto_relative_url(
|
||||
m_rutube.group(1).replace('\\', ''))
|
||||
return self.url_result(rutube_url)
|
||||
|
||||
m_opts = re.search(r'(?s)var\s+opts\s*=\s*({.*?});', info_page)
|
||||
if m_opts:
|
||||
m_opts_url = re.search(r"url\s*:\s*'([^']+)", m_opts.group(1))
|
||||
|
102
youtube_dl/extractor/webofstories.py
Normal file
102
youtube_dl/extractor/webofstories.py
Normal file
@@ -0,0 +1,102 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import int_or_none
|
||||
|
||||
|
||||
class WebOfStoriesIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?webofstories\.com/play/(?:[^/]+/)?(?P<id>[0-9]+)'
|
||||
_VIDEO_DOMAIN = 'http://eu-mobile.webofstories.com/'
|
||||
_GREAT_LIFE_STREAMER = 'rtmp://eu-cdn1.webofstories.com/cfx/st/'
|
||||
_USER_STREAMER = 'rtmp://eu-users.webofstories.com/cfx/st/'
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://www.webofstories.com/play/hans.bethe/71',
|
||||
'md5': '373e4dd915f60cfe3116322642ddf364',
|
||||
'info_dict': {
|
||||
'id': '4536',
|
||||
'ext': 'mp4',
|
||||
'title': 'The temperature of the sun',
|
||||
'thumbnail': 're:^https?://.*\.jpg$',
|
||||
'description': 'Hans Bethe talks about calculating the temperature of the sun',
|
||||
'duration': 238,
|
||||
}
|
||||
},
|
||||
{
|
||||
'url': 'http://www.webofstories.com/play/55908',
|
||||
'md5': '2985a698e1fe3211022422c4b5ed962c',
|
||||
'info_dict': {
|
||||
'id': '55908',
|
||||
'ext': 'mp4',
|
||||
'title': 'The story of Gemmata obscuriglobus',
|
||||
'thumbnail': 're:^https?://.*\.jpg$',
|
||||
'description': 'Planctomycete talks about The story of Gemmata obscuriglobus',
|
||||
'duration': 169,
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
title = self._og_search_title(webpage)
|
||||
description = self._html_search_meta('description', webpage)
|
||||
thumbnail = self._og_search_thumbnail(webpage)
|
||||
|
||||
story_filename = self._search_regex(
|
||||
r'\.storyFileName\("([^"]+)"\)', webpage, 'story filename')
|
||||
speaker_id = self._search_regex(
|
||||
r'\.speakerId\("([^"]+)"\)', webpage, 'speaker ID')
|
||||
story_id = self._search_regex(
|
||||
r'\.storyId\((\d+)\)', webpage, 'story ID')
|
||||
speaker_type = self._search_regex(
|
||||
r'\.speakerType\("([^"]+)"\)', webpage, 'speaker type')
|
||||
great_life = self._search_regex(
|
||||
r'isGreatLifeStory\s*=\s*(true|false)', webpage, 'great life story')
|
||||
is_great_life_series = great_life == 'true'
|
||||
duration = int_or_none(self._search_regex(
|
||||
r'\.duration\((\d+)\)', webpage, 'duration', fatal=False))
|
||||
|
||||
# URL building, see: http://www.webofstories.com/scripts/player.js
|
||||
ms_prefix = ''
|
||||
if speaker_type.lower() == 'ms':
|
||||
ms_prefix = 'mini_sites/'
|
||||
|
||||
if is_great_life_series:
|
||||
mp4_url = '{0:}lives/{1:}/{2:}.mp4'.format(
|
||||
self._VIDEO_DOMAIN, speaker_id, story_filename)
|
||||
rtmp_ext = 'flv'
|
||||
streamer = self._GREAT_LIFE_STREAMER
|
||||
play_path = 'stories/{0:}/{1:}'.format(
|
||||
speaker_id, story_filename)
|
||||
else:
|
||||
mp4_url = '{0:}{1:}{2:}/{3:}.mp4'.format(
|
||||
self._VIDEO_DOMAIN, ms_prefix, speaker_id, story_filename)
|
||||
rtmp_ext = 'mp4'
|
||||
streamer = self._USER_STREAMER
|
||||
play_path = 'mp4:{0:}{1:}/{2}.mp4'.format(
|
||||
ms_prefix, speaker_id, story_filename)
|
||||
|
||||
formats = [{
|
||||
'format_id': 'mp4_sd',
|
||||
'url': mp4_url,
|
||||
}, {
|
||||
'format_id': 'rtmp_sd',
|
||||
'page_url': url,
|
||||
'url': streamer,
|
||||
'ext': rtmp_ext,
|
||||
'play_path': play_path,
|
||||
}]
|
||||
|
||||
self._sort_formats(formats)
|
||||
|
||||
return {
|
||||
'id': story_id,
|
||||
'title': title,
|
||||
'formats': formats,
|
||||
'thumbnail': thumbnail,
|
||||
'description': description,
|
||||
'duration': duration,
|
||||
}
|
@@ -1206,9 +1206,6 @@ class YoutubePlaylistIE(YoutubeBaseInfoExtractor):
|
||||
if playlist_id.startswith('RD'):
|
||||
# Mixes require a custom extraction process
|
||||
return self._extract_mix(playlist_id)
|
||||
if playlist_id.startswith('TL'):
|
||||
raise ExtractorError('For downloading YouTube.com top lists, use '
|
||||
'the "yttoplist" keyword, for example "youtube-dl \'yttoplist:music:Top Tracks\'"', expected=True)
|
||||
|
||||
url = self._TEMPLATE_URL % playlist_id
|
||||
page = self._download_webpage(url, playlist_id)
|
||||
@@ -1254,49 +1251,6 @@ class YoutubePlaylistIE(YoutubeBaseInfoExtractor):
|
||||
return self.playlist_result(url_results, playlist_id, playlist_title)
|
||||
|
||||
|
||||
class YoutubeTopListIE(YoutubePlaylistIE):
|
||||
IE_NAME = 'youtube:toplist'
|
||||
IE_DESC = ('YouTube.com top lists, "yttoplist:{channel}:{list title}"'
|
||||
' (Example: "yttoplist:music:Top Tracks")')
|
||||
_VALID_URL = r'yttoplist:(?P<chann>.*?):(?P<title>.*?)$'
|
||||
_TESTS = [{
|
||||
'url': 'yttoplist:music:Trending',
|
||||
'playlist_mincount': 5,
|
||||
'skip': 'Only works for logged-in users',
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
channel = mobj.group('chann')
|
||||
title = mobj.group('title')
|
||||
query = compat_urllib_parse.urlencode({'title': title})
|
||||
channel_page = self._download_webpage(
|
||||
'https://www.youtube.com/%s' % channel, title)
|
||||
link = self._html_search_regex(
|
||||
r'''(?x)
|
||||
<a\s+href="([^"]+)".*?>\s*
|
||||
<span\s+class="branded-page-module-title-text">\s*
|
||||
<span[^>]*>.*?%s.*?</span>''' % re.escape(query),
|
||||
channel_page, 'list')
|
||||
url = compat_urlparse.urljoin('https://www.youtube.com/', link)
|
||||
|
||||
video_re = r'data-index="\d+".*?data-video-id="([0-9A-Za-z_-]{11})"'
|
||||
ids = []
|
||||
# sometimes the webpage doesn't contain the videos
|
||||
# retry until we get them
|
||||
for i in itertools.count(0):
|
||||
msg = 'Downloading Youtube mix'
|
||||
if i > 0:
|
||||
msg += ', retry #%d' % i
|
||||
|
||||
webpage = self._download_webpage(url, title, msg)
|
||||
ids = orderedSet(re.findall(video_re, webpage))
|
||||
if ids:
|
||||
break
|
||||
url_results = self._ids_to_results(ids)
|
||||
return self.playlist_result(url_results, playlist_title=title)
|
||||
|
||||
|
||||
class YoutubeChannelIE(InfoExtractor):
|
||||
IE_DESC = 'YouTube.com channels'
|
||||
_VALID_URL = r'https?://(?:youtu\.be|(?:\w+\.)?youtube(?:-nocookie)?\.com)/channel/(?P<id>[0-9A-Za-z_-]+)'
|
||||
|
@@ -520,7 +520,7 @@ class FFmpegMetadataPP(FFmpegPostProcessor):
|
||||
class FFmpegMergerPP(FFmpegPostProcessor):
|
||||
def run(self, info):
|
||||
filename = info['filepath']
|
||||
args = ['-c', 'copy', '-map', '0:v:0', '-map', '1:a:0', '-shortest']
|
||||
args = ['-c', 'copy', '-map', '0:v:0', '-map', '1:a:0']
|
||||
self._downloader.to_screen('[ffmpeg] Merging formats into "%s"' % filename)
|
||||
self.run_ffmpeg_multiple_files(info['__files_to_merge'], filename, args)
|
||||
return True, info
|
||||
|
@@ -1550,3 +1550,13 @@ def ytdl_is_updateable():
|
||||
def args_to_str(args):
|
||||
# Get a short string representation for a subprocess command
|
||||
return ' '.join(shlex_quote(a) for a in args)
|
||||
|
||||
|
||||
def urlhandle_detect_ext(url_handle):
|
||||
try:
|
||||
url_handle.headers
|
||||
getheader = lambda h: url_handle.headers[h]
|
||||
except AttributeError: # Python < 3
|
||||
getheader = url_handle.info().getheader
|
||||
|
||||
return getheader('Content-Type').split("/")[1]
|
||||
|
@@ -1,3 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__version__ = '2015.01.03'
|
||||
__version__ = '2015.01.05'
|
||||
|
Reference in New Issue
Block a user