aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md6
-rw-r--r--test/test_youtube_lists.py8
-rw-r--r--youtube_dl/YoutubeDL.py1
-rw-r--r--youtube_dl/__init__.py14
-rw-r--r--youtube_dl/extractor/dailymotion.py43
-rw-r--r--youtube_dl/extractor/francetv.py16
-rw-r--r--youtube_dl/extractor/ign.py54
-rw-r--r--youtube_dl/extractor/rtlnow.py9
-rw-r--r--youtube_dl/extractor/youtube.py29
-rw-r--r--youtube_dl/utils.py6
-rw-r--r--youtube_dl/version.py2
11 files changed, 150 insertions, 38 deletions
diff --git a/README.md b/README.md
index fc8070c37..14d62b189 100644
--- a/README.md
+++ b/README.md
@@ -31,8 +31,9 @@ which means you can modify it, redistribute it or use it however you like.
--proxy URL Use the specified HTTP/HTTPS proxy
--no-check-certificate Suppress HTTPS certificate validation.
--cache-dir None Location in the filesystem where youtube-dl can
- store downloaded information permanently.
- ~/.youtube-dl/cache by default
+ store downloaded information permanently. By
+ default $XDG_CACHE_HOME/youtube-dl or ~/.cache
+ /youtube-dl .
--no-cache-dir Disable filesystem caching
## Video Selection:
@@ -50,6 +51,7 @@ which means you can modify it, redistribute it or use it however you like.
--date DATE download only videos uploaded in this date
--datebefore DATE download only videos uploaded before this date
--dateafter DATE download only videos uploaded after this date
+ --no-playlist download only the currently playing video
## Download Options:
-r, --rate-limit LIMIT maximum download rate (e.g. 50k or 44.6m)
diff --git a/test/test_youtube_lists.py b/test/test_youtube_lists.py
index dd9e292b0..53e65816d 100644
--- a/test/test_youtube_lists.py
+++ b/test/test_youtube_lists.py
@@ -27,6 +27,14 @@ class TestYoutubeLists(unittest.TestCase):
ytie_results = [YoutubeIE()._extract_id(url['url']) for url in result['entries']]
self.assertEqual(ytie_results, [ 'bV9L5Ht9LgY', 'FXxLjLQi3Fg', 'tU3Bgo5qJZE'])
+ def test_youtube_playlist_noplaylist(self):
+ dl = FakeYDL()
+ dl.params['noplaylist'] = True
+ ie = YoutubePlaylistIE(dl)
+ result = ie.extract('https://www.youtube.com/watch?v=FXxLjLQi3Fg&list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re')
+ self.assertEqual(result['_type'], 'url')
+ self.assertEqual(YoutubeIE()._extract_id(result['url']), 'FXxLjLQi3Fg')
+
def test_issue_673(self):
dl = FakeYDL()
ie = YoutubePlaylistIE(dl)
diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py
index 44a272e7e..2503fd09b 100644
--- a/youtube_dl/YoutubeDL.py
+++ b/youtube_dl/YoutubeDL.py
@@ -83,6 +83,7 @@ class YoutubeDL(object):
skip_download: Skip the actual download of the video file
cachedir: Location of the cache files in the filesystem.
None to disable filesystem cache.
+ noplaylist: Download single video instead of a playlist if in doubt.
The following parameters are not used by YoutubeDL itself, they are used by
the FileDownloader:
diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py
index 28a7bdd92..03df835f2 100644
--- a/youtube_dl/__init__.py
+++ b/youtube_dl/__init__.py
@@ -168,8 +168,8 @@ def parseOpts(overrideArguments=None):
general.add_option('--proxy', dest='proxy', default=None, help='Use the specified HTTP/HTTPS proxy', metavar='URL')
general.add_option('--no-check-certificate', action='store_true', dest='no_check_certificate', default=False, help='Suppress HTTPS certificate validation.')
general.add_option(
- '--cache-dir', dest='cachedir', default=u'~/.youtube-dl/cache',
- help='Location in the filesystem where youtube-dl can store downloaded information permanently. %default by default')
+ '--cache-dir', dest='cachedir', default=get_cachedir(),
+ help='Location in the filesystem where youtube-dl can store downloaded information permanently. By default $XDG_CACHE_HOME/youtube-dl or ~/.cache/youtube-dl .')
general.add_option(
'--no-cache-dir', action='store_const', const=None, dest='cachedir',
help='Disable filesystem caching')
@@ -187,6 +187,7 @@ def parseOpts(overrideArguments=None):
selection.add_option('--date', metavar='DATE', dest='date', help='download only videos uploaded in this date', default=None)
selection.add_option('--datebefore', metavar='DATE', dest='datebefore', help='download only videos uploaded before this date', default=None)
selection.add_option('--dateafter', metavar='DATE', dest='dateafter', help='download only videos uploaded after this date', default=None)
+ selection.add_option('--no-playlist', action='store_true', dest='noplaylist', help='download only the currently playing video', default=False)
authentication.add_option('-u', '--username',
@@ -369,9 +370,13 @@ def parseOpts(overrideArguments=None):
else:
xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
if xdg_config_home:
- userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf')
+ userConfFile = os.path.join(xdg_config_home, 'youtube-dl', 'config')
+ if not os.path.isfile(userConfFile):
+ userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf')
else:
- userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
+ userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl', 'config')
+ if not os.path.isfile(userConfFile):
+ userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
systemConf = _readOptions('/etc/youtube-dl.conf')
userConf = _readOptions(userConfFile)
commandLineConf = sys.argv[1:]
@@ -599,6 +604,7 @@ def _real_main(argv=None):
'progress_with_newline': opts.progress_with_newline,
'playliststart': opts.playliststart,
'playlistend': opts.playlistend,
+ 'noplaylist': opts.noplaylist,
'logtostderr': opts.outtmpl == '-',
'consoletitle': opts.consoletitle,
'nopart': opts.nopart,
diff --git a/youtube_dl/extractor/dailymotion.py b/youtube_dl/extractor/dailymotion.py
index 3f012aedc..259806f38 100644
--- a/youtube_dl/extractor/dailymotion.py
+++ b/youtube_dl/extractor/dailymotion.py
@@ -27,15 +27,31 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):
_VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/(?:embed/)?video/([^/]+)'
IE_NAME = u'dailymotion'
- _TEST = {
- u'url': u'http://www.dailymotion.com/video/x33vw9_tutoriel-de-youtubeur-dl-des-video_tech',
- u'file': u'x33vw9.mp4',
- u'md5': u'392c4b85a60a90dc4792da41ce3144eb',
- u'info_dict': {
- u"uploader": u"Amphora Alex and Van .",
- u"title": u"Tutoriel de Youtubeur\"DL DES VIDEO DE YOUTUBE\""
- }
- }
+ _TESTS = [
+ {
+ u'url': u'http://www.dailymotion.com/video/x33vw9_tutoriel-de-youtubeur-dl-des-video_tech',
+ u'file': u'x33vw9.mp4',
+ u'md5': u'392c4b85a60a90dc4792da41ce3144eb',
+ u'info_dict': {
+ u"uploader": u"Amphora Alex and Van .",
+ u"title": u"Tutoriel de Youtubeur\"DL DES VIDEO DE YOUTUBE\""
+ }
+ },
+ # Vevo video
+ {
+ u'url': u'http://www.dailymotion.com/video/x149uew_katy-perry-roar-official_musi',
+ u'file': u'USUV71301934.mp4',
+ u'info_dict': {
+ u'title': u'Roar (Official)',
+ u'uploader': u'Katy Perry',
+ u'upload_date': u'20130905',
+ },
+ u'params': {
+ u'skip_download': True,
+ },
+ u'skip': u'VEVO is only available in some countries',
+ },
+ ]
def _real_extract(self, url):
# Extract id and simplified title from URL
@@ -53,6 +69,15 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):
# Extract URL, uploader and title from webpage
self.report_extraction(video_id)
+ # It may just embed a vevo video:
+ m_vevo = re.search(
+ r'<link rel="video_src" href="[^"]*?vevo.com[^"]*?videoId=(?P<id>[\w]*)',
+ webpage)
+ if m_vevo is not None:
+ vevo_id = m_vevo.group('id')
+ self.to_screen(u'Vevo video detected: %s' % vevo_id)
+ return self.url_result(u'vevo:%s' % vevo_id, ie='Vevo')
+
video_uploader = self._search_regex([r'(?im)<span class="owner[^\"]+?">[^<]+?<a [^>]+?>([^<]+?)</a>',
# Looking for official user
r'<(?:span|a) .*?rel="author".*?>([^<]+?)</'],
diff --git a/youtube_dl/extractor/francetv.py b/youtube_dl/extractor/francetv.py
index b1530e549..461dac8ef 100644
--- a/youtube_dl/extractor/francetv.py
+++ b/youtube_dl/extractor/francetv.py
@@ -70,7 +70,11 @@ class FranceTvInfoIE(FranceTVBaseInfoExtractor):
class France2IE(FranceTVBaseInfoExtractor):
IE_NAME = u'france2.fr'
- _VALID_URL = r'https?://www\.france2\.fr/emissions/.*?/videos/(?P<id>\d+)'
+ _VALID_URL = r'''(?x)https?://www\.france2\.fr/
+ (?:
+ emissions/.*?/videos/(?P<id>\d+)
+ | emission/(?P<key>[^/?]+)
+ )'''
_TEST = {
u'url': u'http://www.france2.fr/emissions/13h15-le-samedi-le-dimanche/videos/75540104',
@@ -86,7 +90,15 @@ class France2IE(FranceTVBaseInfoExtractor):
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
- video_id = mobj.group('id')
+ if mobj.group('key'):
+ webpage = self._download_webpage(url, mobj.group('key'))
+ video_id = self._html_search_regex(
+ r'''(?x)<div\s+class="video-player">\s*
+ <a\s+href="http://videos.francetv.fr/video/([0-9]+)"\s+
+ class="francetv-video-player">''',
+ webpage, u'video ID')
+ else:
+ video_id = mobj.group('id')
return self._extract_video(video_id)
diff --git a/youtube_dl/extractor/ign.py b/youtube_dl/extractor/ign.py
index b1c84278a..c52146f7d 100644
--- a/youtube_dl/extractor/ign.py
+++ b/youtube_dl/extractor/ign.py
@@ -13,7 +13,7 @@ class IGNIE(InfoExtractor):
Some videos of it.ign.com are also supported
"""
- _VALID_URL = r'https?://.+?\.ign\.com/(?P<type>videos|show_videos|articles)(/.+)?/(?P<name_or_id>.+)'
+ _VALID_URL = r'https?://.+?\.ign\.com/(?P<type>videos|show_videos|articles|(?:[^/]*/feature))(/.+)?/(?P<name_or_id>.+)'
IE_NAME = u'ign.com'
_CONFIG_URL_TEMPLATE = 'http://www.ign.com/videos/configs/id/%s.config'
@@ -21,15 +21,39 @@ class IGNIE(InfoExtractor):
r'id="my_show_video">.*?<p>(.*?)</p>',
]
- _TEST = {
- u'url': u'http://www.ign.com/videos/2013/06/05/the-last-of-us-review',
- u'file': u'8f862beef863986b2785559b9e1aa599.mp4',
- u'md5': u'eac8bdc1890980122c3b66f14bdd02e9',
- u'info_dict': {
- u'title': u'The Last of Us Review',
- u'description': u'md5:c8946d4260a4d43a00d5ae8ed998870c',
- }
- }
+ _TESTS = [
+ {
+ u'url': u'http://www.ign.com/videos/2013/06/05/the-last-of-us-review',
+ u'file': u'8f862beef863986b2785559b9e1aa599.mp4',
+ u'md5': u'eac8bdc1890980122c3b66f14bdd02e9',
+ u'info_dict': {
+ u'title': u'The Last of Us Review',
+ u'description': u'md5:c8946d4260a4d43a00d5ae8ed998870c',
+ }
+ },
+ {
+ u'url': u'http://me.ign.com/en/feature/15775/100-little-things-in-gta-5-that-will-blow-your-mind',
+ u'playlist': [
+ {
+ u'file': u'5ebbd138523268b93c9141af17bec937.mp4',
+ u'info_dict': {
+ u'title': u'GTA 5 Video Review',
+ u'description': u'Rockstar drops the mic on this generation of games. Watch our review of the masterly Grand Theft Auto V.',
+ },
+ },
+ {
+ u'file': u'638672ee848ae4ff108df2a296418ee2.mp4',
+ u'info_dict': {
+ u'title': u'GTA 5\'s Twisted Beauty in Super Slow Motion',
+ u'description': u'The twisted beauty of GTA 5 in stunning slow motion.',
+ },
+ },
+ ],
+ u'params': {
+ u'skip_download': True,
+ },
+ },
+ ]
def _find_video_id(self, webpage):
res_id = [r'data-video-id="(.+?)"',
@@ -46,6 +70,13 @@ class IGNIE(InfoExtractor):
if page_type == 'articles':
video_url = self._search_regex(r'var videoUrl = "(.+?)"', webpage, u'video url')
return self.url_result(video_url, ie='IGN')
+ elif page_type != 'video':
+ multiple_urls = re.findall(
+ '<param name="flashvars" value="[^"]*?url=(https?://www\.ign\.com/videos/.*?)["&]',
+ webpage)
+ if multiple_urls:
+ return [self.url_result(u, ie='IGN') for u in multiple_urls]
+
video_id = self._find_video_id(webpage)
result = self._get_video_info(video_id)
description = self._html_search_regex(self._DESCRIPTION_RE,
@@ -87,6 +118,9 @@ class OneUPIE(IGNIE):
}
}
+ # Override IGN tests
+ _TESTS = []
+
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
id = mobj.group('name_or_id')
diff --git a/youtube_dl/extractor/rtlnow.py b/youtube_dl/extractor/rtlnow.py
index fe66cc6e5..580f9e6d5 100644
--- a/youtube_dl/extractor/rtlnow.py
+++ b/youtube_dl/extractor/rtlnow.py
@@ -110,14 +110,17 @@ class RTLnowIE(InfoExtractor):
webpage, u'playerdata_url')
playerdata = self._download_webpage(playerdata_url, video_id)
- mobj = re.search(r'<title><!\[CDATA\[(?P<description>.+?)\s+- (?:Sendung )?vom (?P<upload_date_d>[0-9]{2})\.(?P<upload_date_m>[0-9]{2})\.(?:(?P<upload_date_Y>[0-9]{4})|(?P<upload_date_y>[0-9]{2})) [0-9]{2}:[0-9]{2} Uhr\]\]></title>', playerdata)
+ mobj = re.search(r'<title><!\[CDATA\[(?P<description>.+?)(?:\s+- (?:Sendung )?vom (?P<upload_date_d>[0-9]{2})\.(?P<upload_date_m>[0-9]{2})\.(?:(?P<upload_date_Y>[0-9]{4})|(?P<upload_date_y>[0-9]{2})) [0-9]{2}:[0-9]{2} Uhr)?\]\]></title>', playerdata)
if mobj:
video_description = mobj.group(u'description')
if mobj.group('upload_date_Y'):
video_upload_date = mobj.group('upload_date_Y')
- else:
+ elif mobj.group('upload_date_y'):
video_upload_date = u'20' + mobj.group('upload_date_y')
- video_upload_date += mobj.group('upload_date_m')+mobj.group('upload_date_d')
+ else:
+ video_upload_date = None
+ if video_upload_date:
+ video_upload_date += mobj.group('upload_date_m')+mobj.group('upload_date_d')
else:
video_description = None
video_upload_date = None
diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py
index 53f13b516..39ff33290 100644
--- a/youtube_dl/extractor/youtube.py
+++ b/youtube_dl/extractor/youtube.py
@@ -23,9 +23,11 @@ from ..utils import (
compat_urllib_error,
compat_urllib_parse,
compat_urllib_request,
+ compat_urlparse,
compat_str,
clean_html,
+ get_cachedir,
get_element_by_id,
ExtractorError,
unescapeHTML,
@@ -420,8 +422,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
# Read from filesystem cache
func_id = '%s_%s_%d' % (player_type, player_id, slen)
assert os.path.basename(func_id) == func_id
- cache_dir = self._downloader.params.get('cachedir',
- u'~/.youtube-dl/cache')
+ cache_dir = get_cachedir(self._downloader.params)
cache_enabled = cache_dir is not None
if cache_enabled:
@@ -1086,7 +1087,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
elif len(s) == 83:
return s[80:63:-1] + s[0] + s[62:0:-1] + s[63]
elif len(s) == 82:
- return s[80:73:-1] + s[81] + s[72:54:-1] + s[2] + s[53:43:-1] + s[0] + s[42:2:-1] + s[43] + s[1] + s[54]
+ return s[80:37:-1] + s[7] + s[36:7:-1] + s[0] + s[6:0:-1] + s[37]
elif len(s) == 81:
return s[56] + s[79:56:-1] + s[41] + s[55:41:-1] + s[80] + s[40:34:-1] + s[0] + s[33:29:-1] + s[34] + s[28:9:-1] + s[29] + s[8:0:-1] + s[9]
elif len(s) == 80:
@@ -1333,9 +1334,11 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
self._downloader.report_warning(u'unable to extract uploader nickname')
# title
- if 'title' not in video_info:
- raise ExtractorError(u'Unable to extract video title')
- video_title = compat_urllib_parse.unquote_plus(video_info['title'][0])
+ if 'title' in video_info:
+ video_title = compat_urllib_parse.unquote_plus(video_info['title'][0])
+ else:
+ self._downloader.report_warning(u'Unable to extract video title')
+ video_title = u'_'
# thumbnail image
# We try first to get a high quality image:
@@ -1390,6 +1393,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
args = info['args']
# Easy way to know if the 's' value is in url_encoded_fmt_stream_map
# this signatures are encrypted
+ if 'url_encoded_fmt_stream_map' not in args:
+ raise ValueError(u'No stream_map present') # caught below
m_s = re.search(r'[&,]s=', args['url_encoded_fmt_stream_map'])
if m_s is not None:
self.to_screen(u'%s: Encrypted signatures detected.' % video_id)
@@ -1523,9 +1528,19 @@ class YoutubePlaylistIE(InfoExtractor):
mobj = re.match(self._VALID_URL, url, re.VERBOSE)
if mobj is None:
raise ExtractorError(u'Invalid URL: %s' % url)
+ playlist_id = mobj.group(1) or mobj.group(2)
+
+ # Check if it's a video-specific URL
+ query_dict = compat_urlparse.parse_qs(compat_urlparse.urlparse(url).query)
+ if 'v' in query_dict:
+ video_id = query_dict['v'][0]
+ if self._downloader.params.get('noplaylist'):
+ self.to_screen(u'Downloading just video %s because of --no-playlist' % video_id)
+ return self.url_result('https://www.youtube.com/watch?v=' + video_id, 'Youtube')
+ else:
+ self.to_screen(u'Downloading playlist PL%s - add --no-playlist to just download video %s' % (playlist_id, video_id))
# Download playlist videos from API
- playlist_id = mobj.group(1) or mobj.group(2)
videos = []
for page_num in itertools.count(1):
diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py
index 201ed255d..f5f9cde99 100644
--- a/youtube_dl/utils.py
+++ b/youtube_dl/utils.py
@@ -824,3 +824,9 @@ def intlist_to_bytes(xs):
return ''.join([chr(x) for x in xs])
else:
return bytes(xs)
+
+
+def get_cachedir(params={}):
+ cache_root = os.environ.get('XDG_CACHE_HOME',
+ os.path.expanduser('~/.cache'))
+ return params.get('cachedir', os.path.join(cache_root, 'youtube-dl'))
diff --git a/youtube_dl/version.py b/youtube_dl/version.py
index e3e5d5538..e773e82da 100644
--- a/youtube_dl/version.py
+++ b/youtube_dl/version.py
@@ -1,2 +1,2 @@
-__version__ = '2013.09.29'
+__version__ = '2013.10.04'