Merge remote-tracking branch 'origin/master'

Conflicts:
	youtube_dl/YoutubeDL.py
This commit is contained in:
Philipp Hagemeister 2013-10-22 22:30:06 +02:00
commit 7853cc5ae1
10 changed files with 162 additions and 102 deletions

View file

@ -94,6 +94,40 @@ def test_format_limit(self):
downloaded = ydl.downloaded_info_dicts[0] downloaded = ydl.downloaded_info_dicts[0]
self.assertEqual(downloaded[u'format_id'], u'excellent') self.assertEqual(downloaded[u'format_id'], u'excellent')
def test_format_selection(self):
formats = [
{u'format_id': u'35', u'ext': u'mp4'},
{u'format_id': u'45', u'ext': u'webm'},
{u'format_id': u'47', u'ext': u'webm'},
{u'format_id': u'2', u'ext': u'flv'},
]
info_dict = {u'formats': formats, u'extractor': u'test'}
ydl = YDL({'format': u'20/47'})
ydl.process_ie_result(info_dict)
downloaded = ydl.downloaded_info_dicts[0]
self.assertEqual(downloaded['format_id'], u'47')
ydl = YDL({'format': u'20/71/worst'})
ydl.process_ie_result(info_dict)
downloaded = ydl.downloaded_info_dicts[0]
self.assertEqual(downloaded['format_id'], u'35')
ydl = YDL()
ydl.process_ie_result(info_dict)
downloaded = ydl.downloaded_info_dicts[0]
self.assertEqual(downloaded['format_id'], u'2')
ydl = YDL({'format': u'webm/mp4'})
ydl.process_ie_result(info_dict)
downloaded = ydl.downloaded_info_dicts[0]
self.assertEqual(downloaded['format_id'], u'47')
ydl = YDL({'format': u'3gp/40/mp4'})
ydl.process_ie_result(info_dict)
downloaded = ydl.downloaded_info_dicts[0]
self.assertEqual(downloaded['format_id'], u'35')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -216,10 +216,10 @@ def report_warning(self, message):
If stderr is a tty file the 'WARNING:' will be colored If stderr is a tty file the 'WARNING:' will be colored
''' '''
if sys.stderr.isatty() and os.name != 'nt': if sys.stderr.isatty() and os.name != 'nt':
_msg_header=u'\033[0;33mWARNING:\033[0m' _msg_header = u'\033[0;33mWARNING:\033[0m'
else: else:
_msg_header=u'WARNING:' _msg_header = u'WARNING:'
warning_message=u'%s %s' % (_msg_header,message) warning_message = u'%s %s' % (_msg_header, message)
self.to_stderr(warning_message) self.to_stderr(warning_message)
def report_error(self, message, tb=None): def report_error(self, message, tb=None):
@ -234,19 +234,6 @@ def report_error(self, message, tb=None):
error_message = u'%s %s' % (_msg_header, message) error_message = u'%s %s' % (_msg_header, message)
self.trouble(error_message, tb) self.trouble(error_message, tb)
def slow_down(self, start_time, byte_counter):
"""Sleep if the download speed is over the rate limit."""
rate_limit = self.params.get('ratelimit', None)
if rate_limit is None or byte_counter == 0:
return
now = time.time()
elapsed = now - start_time
if elapsed <= 0.0:
return
speed = float(byte_counter) / elapsed
if speed > rate_limit:
time.sleep((byte_counter - rate_limit * (now - start_time)) / rate_limit)
def report_writedescription(self, descfn): def report_writedescription(self, descfn):
""" Report that the description file is being written """ """ Report that the description file is being written """
self.to_screen(u'[info] Writing video description to: ' + descfn) self.to_screen(u'[info] Writing video description to: ' + descfn)
@ -403,7 +390,7 @@ def process_ie_result(self, ie_result, download=True, extra_info={}):
elif result_type == 'playlist': elif result_type == 'playlist':
# We process each entry in the playlist # We process each entry in the playlist
playlist = ie_result.get('title', None) or ie_result.get('id', None) playlist = ie_result.get('title', None) or ie_result.get('id', None)
self.to_screen(u'[download] Downloading playlist: %s' % playlist) self.to_screen(u'[download] Downloading playlist: %s' % playlist)
playlist_results = [] playlist_results = []
@ -421,12 +408,12 @@ def process_ie_result(self, ie_result, download=True, extra_info={}):
self.to_screen(u"[%s] playlist '%s': Collected %d video ids (downloading %d of them)" % self.to_screen(u"[%s] playlist '%s': Collected %d video ids (downloading %d of them)" %
(ie_result['extractor'], playlist, n_all_entries, n_entries)) (ie_result['extractor'], playlist, n_all_entries, n_entries))
for i,entry in enumerate(entries,1): for i, entry in enumerate(entries, 1):
self.to_screen(u'[download] Downloading video #%s of %s' %(i, n_entries)) self.to_screen(u'[download] Downloading video #%s of %s' % (i, n_entries))
extra = { extra = {
'playlist': playlist, 'playlist': playlist,
'playlist_index': i + playliststart, 'playlist_index': i + playliststart,
} }
if not 'extractor' in entry: if not 'extractor' in entry:
# We set the extractor, if it's an url it will be set then to # We set the extractor, if it's an url it will be set then to
# the new extractor, but if it's already a video we must make # the new extractor, but if it's already a video we must make
@ -450,6 +437,22 @@ def _fixup(r):
else: else:
raise Exception('Invalid result type: %s' % result_type) raise Exception('Invalid result type: %s' % result_type)
def select_format(self, format_spec, available_formats):
if format_spec == 'best' or format_spec is None:
return available_formats[-1]
elif format_spec == 'worst':
return available_formats[0]
else:
extensions = [u'mp4', u'flv', u'webm', u'3gp']
if format_spec in extensions:
filter_f = lambda f: f['ext'] == format_spec
else:
filter_f = lambda f: f['format_id'] == format_spec
matches = list(filter(filter_f, available_formats))
if matches:
return matches[-1]
return None
def process_video_result(self, info_dict, download=True): def process_video_result(self, info_dict, download=True):
assert info_dict.get('_type', 'video') == 'video' assert info_dict.get('_type', 'video') == 'video'
@ -460,7 +463,8 @@ def process_video_result(self, info_dict, download=True):
# This extractors handle format selection themselves # This extractors handle format selection themselves
if info_dict['extractor'] in [u'youtube', u'Youku', u'YouPorn', u'mixcloud']: if info_dict['extractor'] in [u'youtube', u'Youku', u'YouPorn', u'mixcloud']:
self.process_info(info_dict) if download:
self.process_info(info_dict)
return info_dict return info_dict
# We now pick which formats have to be downloaded # We now pick which formats have to be downloaded
@ -472,17 +476,14 @@ def process_video_result(self, info_dict, download=True):
# We check that all the formats have the format and format_id fields # We check that all the formats have the format and format_id fields
for (i, format) in enumerate(formats): for (i, format) in enumerate(formats):
if format.get('format') is None:
if format.get('height') is not None:
if format.get('width') is not None:
format_desc = u'%sx%s' % (format['width'], format['height'])
else:
format_desc = u'%sp' % format['height']
else:
format_desc = '???'
format['format'] = format_desc
if format.get('format_id') is None: if format.get('format_id') is None:
format['format_id'] = compat_str(i) format['format_id'] = compat_str(i)
if format.get('format') is None:
format['format'] = u'{id} - {res}{note}'.format(
id=format['format_id'],
res=self.format_resolution(format),
note=u' ({})'.format(format['format_note']) if format.get('format_note') is not None else '',
)
if self.params.get('listformats', None): if self.params.get('listformats', None):
self.list_formats(info_dict) self.list_formats(info_dict)
@ -504,22 +505,20 @@ def _free_formats_key(f):
formats = sorted(formats, key=_free_formats_key) formats = sorted(formats, key=_free_formats_key)
req_format = self.params.get('format', 'best') req_format = self.params.get('format', 'best')
if req_format is None:
req_format = 'best'
formats_to_download = [] formats_to_download = []
if req_format == 'best' or req_format is None:
formats_to_download = [formats[-1]]
elif req_format == 'worst':
formats_to_download = [formats[0]]
# The -1 is for supporting YoutubeIE # The -1 is for supporting YoutubeIE
elif req_format in ('-1', 'all'): if req_format in ('-1', 'all'):
formats_to_download = formats formats_to_download = formats
else: else:
# We can accept formats requestd in the format: 34/10/5, we pick # We can accept formats requestd in the format: 34/5/best, we pick
# the first that is available, starting from left # the first that is available, starting from left
req_formats = req_format.split('/') req_formats = req_format.split('/')
for rf in req_formats: for rf in req_formats:
matches = filter(lambda f:f['format_id'] == rf ,formats) selected_format = self.select_format(rf, formats)
if matches: if selected_format is not None:
formats_to_download = [matches[0]] formats_to_download = [selected_format]
break break
if not formats_to_download: if not formats_to_download:
raise ExtractorError(u'requested format not available') raise ExtractorError(u'requested format not available')
@ -610,20 +609,20 @@ def process_info(self, info_dict):
if self.params.get('writeannotations', False): if self.params.get('writeannotations', False):
try: try:
annofn = filename + u'.annotations.xml' annofn = filename + u'.annotations.xml'
self.report_writeannotations(annofn) self.report_writeannotations(annofn)
with io.open(encodeFilename(annofn), 'w', encoding='utf-8') as annofile: with io.open(encodeFilename(annofn), 'w', encoding='utf-8') as annofile:
annofile.write(info_dict['annotations']) annofile.write(info_dict['annotations'])
except (KeyError, TypeError): except (KeyError, TypeError):
self.report_warning(u'There are no annotations to write.') self.report_warning(u'There are no annotations to write.')
except (OSError, IOError): except (OSError, IOError):
self.report_error(u'Cannot write annotations file: ' + annofn) self.report_error(u'Cannot write annotations file: ' + annofn)
return return
subtitles_are_requested = any([self.params.get('writesubtitles', False), subtitles_are_requested = any([self.params.get('writesubtitles', False),
self.params.get('writeautomaticsub')]) self.params.get('writeautomaticsub')])
if subtitles_are_requested and 'subtitles' in info_dict and info_dict['subtitles']: if subtitles_are_requested and 'subtitles' in info_dict and info_dict['subtitles']:
# subtitles download errors are already managed as troubles in relevant IE # subtitles download errors are already managed as troubles in relevant IE
# that way it will silently go on when used with unsupporting IE # that way it will silently go on when used with unsupporting IE
subtitles = info_dict['subtitles'] subtitles = info_dict['subtitles']
@ -645,7 +644,7 @@ def process_info(self, info_dict):
infofn = filename + u'.info.json' infofn = filename + u'.info.json'
self.report_writeinfojson(infofn) self.report_writeinfojson(infofn)
try: try:
json_info_dict = dict((k, v) for k,v in info_dict.items() if not k in ['urlhandle']) json_info_dict = dict((k, v) for k, v in info_dict.items() if not k in ['urlhandle'])
write_json_file(json_info_dict, encodeFilename(infofn)) write_json_file(json_info_dict, encodeFilename(infofn))
except (OSError, IOError): except (OSError, IOError):
self.report_error(u'Cannot write metadata to JSON file ' + infofn) self.report_error(u'Cannot write metadata to JSON file ' + infofn)
@ -715,7 +714,7 @@ def post_process(self, filename, ie_info):
keep_video = None keep_video = None
for pp in self._pps: for pp in self._pps:
try: try:
keep_video_wish,new_info = pp.run(info) keep_video_wish, new_info = pp.run(info)
if keep_video_wish is not None: if keep_video_wish is not None:
if keep_video_wish: if keep_video_wish:
keep_video = keep_video_wish keep_video = keep_video_wish
@ -754,16 +753,31 @@ def record_download_archive(self, info_dict):
with locked_file(fn, 'a', encoding='utf-8') as archive_file: with locked_file(fn, 'a', encoding='utf-8') as archive_file:
archive_file.write(vid_id + u'\n') archive_file.write(vid_id + u'\n')
@staticmethod
def format_resolution(format):
if format.get('height') is not None:
if format.get('width') is not None:
res = u'%sx%s' % (format['width'], format['height'])
else:
res = u'%sp' % format['height']
else:
res = '???'
return res
def list_formats(self, info_dict): def list_formats(self, info_dict):
formats_s = [] formats_s = []
for format in info_dict.get('formats', [info_dict]): for format in info_dict.get('formats', [info_dict]):
formats_s.append("%s\t:\t%s\t[%s]" % (format['format_id'], formats_s.append(u'%-15s: %-5s %-15s[%s]' % (
format['ext'], format['format_id'],
format.get('format', '???'), format['ext'],
) format.get('format_note') or '-',
) self.format_resolution(format),
)
)
if len(formats_s) != 1: if len(formats_s) != 1:
formats_s[0] += ' (worst)' formats_s[0] += ' (worst)'
formats_s[-1] += ' (best)' formats_s[-1] += ' (best)'
formats_s = "\n".join(formats_s) formats_s = "\n".join(formats_s)
self.to_screen(u"[info] Available formats for %s:\nformat code\textension\n%s" % (info_dict['id'], formats_s)) self.to_screen(u'[info] Available formats for %s:\n'
u'format code extension note resolution\n%s' % (
info_dict['id'], formats_s))

View file

@ -174,12 +174,27 @@ def _match_lang(f):
# Some formats use the m3u8 protocol # Some formats use the m3u8 protocol
formats = filter(lambda f: f.get('videoFormat') != 'M3U8', formats) formats = filter(lambda f: f.get('videoFormat') != 'M3U8', formats)
# We order the formats by quality # We order the formats by quality
formats = sorted(formats, key=lambda f: int(f.get('height',-1))) formats = list(formats) # in python3 filter returns an iterator
if re.match(r'[A-Z]Q', formats[0]['quality']) is not None:
sort_key = lambda f: ['HQ', 'MQ', 'EQ', 'SQ'].index(f['quality'])
else:
sort_key = lambda f: int(f.get('height',-1))
formats = sorted(formats, key=sort_key)
# Prefer videos without subtitles in the same language # Prefer videos without subtitles in the same language
formats = sorted(formats, key=lambda f: re.match(r'VO(F|A)-STM\1', f.get('versionCode', '')) is None) formats = sorted(formats, key=lambda f: re.match(r'VO(F|A)-STM\1', f.get('versionCode', '')) is None)
# Pick the best quality # Pick the best quality
def _format(format_info): def _format(format_info):
quality = format_info['quality']
m_quality = re.match(r'\w*? - (\d*)p', quality)
if m_quality is not None:
quality = m_quality.group(1)
if format_info.get('versionCode') is not None:
format_id = u'%s-%s' % (quality, format_info['versionCode'])
else:
format_id = quality
info = { info = {
'format_id': format_id,
'format_note': format_info.get('versionLibelle'),
'width': format_info.get('width'), 'width': format_info.get('width'),
'height': format_info.get('height'), 'height': format_info.get('height'),
} }
@ -192,8 +207,6 @@ def _format(format_info):
info['ext'] = determine_ext(info['url']) info['ext'] = determine_ext(info['url'])
return info return info
info_dict['formats'] = [_format(f) for f in formats] info_dict['formats'] = [_format(f) for f in formats]
# TODO: Remove when #980 has been merged
info_dict.update(info_dict['formats'][-1])
return info_dict return info_dict
@ -207,7 +220,7 @@ class ArteTVCreativeIE(ArteTVPlus7IE):
u'url': u'http://creative.arte.tv/de/magazin/agentur-amateur-corporate-design', u'url': u'http://creative.arte.tv/de/magazin/agentur-amateur-corporate-design',
u'file': u'050489-002.mp4', u'file': u'050489-002.mp4',
u'info_dict': { u'info_dict': {
u'title': u'Agentur Amateur #2 - Corporate Design', u'title': u'Agentur Amateur / Agence Amateur #2 : Corporate Design',
}, },
} }

View file

@ -61,9 +61,12 @@ class InfoExtractor(object):
* ext Will be calculated from url if missing * ext Will be calculated from url if missing
* format A human-readable description of the format * format A human-readable description of the format
("mp4 container with h264/opus"). ("mp4 container with h264/opus").
Calculated from width and height if missing. Calculated from the format_id, width, height
and format_note fields if missing.
* format_id A short description of the format * format_id A short description of the format
("mp4_h264_opus" or "19") ("mp4_h264_opus" or "19")
* format_note Additional info about the format
("3D" or "DASH video")
* width Width of the video, if known * width Width of the video, if known
* height Height of the video, if known * height Height of the video, if known

View file

@ -41,9 +41,9 @@ def _real_extract(self, url):
# Extract update date # Extract update date
upload_date = self._html_search_regex( upload_date = self._html_search_regex(
r'''(?x)<a.+?class="o-T-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) webpage, u'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")

View file

@ -19,7 +19,7 @@ class InternetVideoArchiveIE(InfoExtractor):
u'info_dict': { u'info_dict': {
u'title': u'SKYFALL', u'title': u'SKYFALL',
u'description': u'In SKYFALL, Bond\'s loyalty to M is tested as her past comes back to haunt her. As MI6 comes under attack, 007 must track down and destroy the threat, no matter how personal the cost.', u'description': u'In SKYFALL, Bond\'s loyalty to M is tested as her past comes back to haunt her. As MI6 comes under attack, 007 must track down and destroy the threat, no matter how personal the cost.',
u'duration': 156, u'duration': 153,
}, },
} }
@ -74,7 +74,7 @@ def _bp(p):
}) })
formats = sorted(formats, key=lambda f: f['bitrate']) formats = sorted(formats, key=lambda f: f['bitrate'])
info = { return {
'id': video_id, 'id': video_id,
'title': item.find('title').text, 'title': item.find('title').text,
'formats': formats, 'formats': formats,
@ -82,6 +82,3 @@ def _bp(p):
'description': item.find('description').text, 'description': item.find('description').text,
'duration': int(attr['duration']), 'duration': int(attr['duration']),
} }
# TODO: Remove when #980 has been merged
info.update(formats[-1])
return info

View file

@ -90,8 +90,8 @@ def _real_extract(self, url):
r'{statusIndex:0,index:0,.*?id:(.*?),'], r'{statusIndex:0,index:0,.*?id:(.*?),'],
webpage, u'category id') webpage, u'category id')
playlist_title = self._html_search_regex( playlist_title = self._html_search_regex(
r'\?catid=%s">(.*?)</a>' % cat_id, r'tab0"[^>]*?>(.*?)</td>',
webpage, u'playlist title', flags=re.DOTALL) webpage, u'playlist title', flags=re.DOTALL).lower().capitalize()
data = compat_urllib_parse.urlencode({ data = compat_urllib_parse.urlencode({
'cid': cat_id, 'cid': cat_id,

View file

@ -16,7 +16,7 @@ class VideoDetectiveIE(InfoExtractor):
u'info_dict': { u'info_dict': {
u'title': u'KICK-ASS 2', u'title': u'KICK-ASS 2',
u'description': u'md5:65ba37ad619165afac7d432eaded6013', u'description': u'md5:65ba37ad619165afac7d432eaded6013',
u'duration': 138, u'duration': 135,
}, },
} }

View file

@ -179,46 +179,45 @@ def _real_extract(self, url, new_video=True):
# Vimeo specific: extract video codec and quality information # Vimeo specific: extract video codec and quality information
# First consider quality, then codecs, then take everything # First consider quality, then codecs, then take everything
# TODO bind to format param codecs = [('vp6', 'flv'), ('vp8', 'flv'), ('h264', 'mp4')]
codecs = [('h264', 'mp4'), ('vp8', 'flv'), ('vp6', 'flv')]
files = { 'hd': [], 'sd': [], 'other': []} files = { 'hd': [], 'sd': [], 'other': []}
config_files = config["video"].get("files") or config["request"].get("files") config_files = config["video"].get("files") or config["request"].get("files")
for codec_name, codec_extension in codecs: for codec_name, codec_extension in codecs:
if codec_name in config_files: for quality in config_files.get(codec_name, []):
if 'hd' in config_files[codec_name]: format_id = '-'.join((codec_name, quality)).lower()
files['hd'].append((codec_name, codec_extension, 'hd')) key = quality if quality in files else 'other'
elif 'sd' in config_files[codec_name]: video_url = None
files['sd'].append((codec_name, codec_extension, 'sd')) if isinstance(config_files[codec_name], dict):
file_info = config_files[codec_name][quality]
video_url = file_info.get('url')
else: else:
files['other'].append((codec_name, codec_extension, config_files[codec_name][0])) file_info = {}
if video_url is None:
video_url = "http://player.vimeo.com/play_redirect?clip_id=%s&sig=%s&time=%s&quality=%s&codecs=%s&type=moogaloop_local&embed_location=" \
%(video_id, sig, timestamp, quality, codec_name.upper())
for quality in ('hd', 'sd', 'other'): files[key].append({
if len(files[quality]) > 0: 'ext': codec_extension,
video_quality = files[quality][0][2] 'url': video_url,
video_codec = files[quality][0][0] 'format_id': format_id,
video_extension = files[quality][0][1] 'width': file_info.get('width'),
self.to_screen(u'%s: Downloading %s file at %s quality' % (video_id, video_codec.upper(), video_quality)) 'height': file_info.get('height'),
break })
else: formats = []
for key in ('other', 'sd', 'hd'):
formats += files[key]
if len(formats) == 0:
raise ExtractorError(u'No known codec found') raise ExtractorError(u'No known codec found')
video_url = None
if isinstance(config_files[video_codec], dict):
video_url = config_files[video_codec][video_quality].get("url")
if video_url is None:
video_url = "http://player.vimeo.com/play_redirect?clip_id=%s&sig=%s&time=%s&quality=%s&codecs=%s&type=moogaloop_local&embed_location=" \
%(video_id, sig, timestamp, video_quality, video_codec.upper())
return [{ return [{
'id': video_id, 'id': video_id,
'url': video_url,
'uploader': video_uploader, 'uploader': video_uploader,
'uploader_id': video_uploader_id, 'uploader_id': video_uploader_id,
'upload_date': video_upload_date, 'upload_date': video_upload_date,
'title': video_title, 'title': video_title,
'ext': video_extension,
'thumbnail': video_thumbnail, 'thumbnail': video_thumbnail,
'description': video_description, 'description': video_description,
'formats': formats,
}] }]

View file

@ -1150,7 +1150,7 @@ def _get_available_automatic_caption(self, video_id, webpage):
list_page = self._download_webpage(list_url, video_id) list_page = self._download_webpage(list_url, video_id)
caption_list = xml.etree.ElementTree.fromstring(list_page.encode('utf-8')) caption_list = xml.etree.ElementTree.fromstring(list_page.encode('utf-8'))
original_lang_node = caption_list.find('track') original_lang_node = caption_list.find('track')
if not original_lang_node or original_lang_node.attrib.get('kind') != 'asr' : if original_lang_node is None or original_lang_node.attrib.get('kind') != 'asr' :
self._downloader.report_warning(u'Video doesn\'t have automatic captions') self._downloader.report_warning(u'Video doesn\'t have automatic captions')
return {} return {}
original_lang = original_lang_node.attrib['lang_code'] original_lang = original_lang_node.attrib['lang_code']