aboutsummaryrefslogtreecommitdiff
path: root/youtube_dl/postprocessor
diff options
context:
space:
mode:
Diffstat (limited to 'youtube_dl/postprocessor')
-rw-r--r--youtube_dl/postprocessor/embedthumbnail.py51
-rw-r--r--youtube_dl/postprocessor/execafterdownload.py7
-rw-r--r--youtube_dl/postprocessor/ffmpeg.py213
-rw-r--r--youtube_dl/postprocessor/metadatafromtitle.py15
-rw-r--r--youtube_dl/postprocessor/xattrpp.py6
5 files changed, 205 insertions, 87 deletions
diff --git a/youtube_dl/postprocessor/embedthumbnail.py b/youtube_dl/postprocessor/embedthumbnail.py
index e606a58de..b6c60e127 100644
--- a/youtube_dl/postprocessor/embedthumbnail.py
+++ b/youtube_dl/postprocessor/embedthumbnail.py
@@ -13,9 +13,13 @@ from ..utils import (
encodeFilename,
PostProcessingError,
prepend_extension,
- shell_quote
+ process_communicate_or_kill,
+ replace_extension,
+ shell_quote,
)
+from ..compat import compat_open as open
+
class EmbedThumbnailPPError(PostProcessingError):
pass
@@ -31,7 +35,8 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
temp_filename = prepend_extension(filename, 'temp')
if not info.get('thumbnails'):
- raise EmbedThumbnailPPError('Thumbnail was not found. Nothing to do.')
+ self._downloader.to_screen('[embedthumbnail] There aren\'t any thumbnails to embed')
+ return [], info
thumbnail_filename = info['thumbnails'][-1]['filename']
@@ -40,6 +45,38 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
'Skipping embedding the thumbnail because the file is missing.')
return [], info
+ def is_webp(path):
+ with open(encodeFilename(path), 'rb') as f:
+ b = f.read(12)
+ return b[0:4] == b'RIFF' and b[8:] == b'WEBP'
+
+ # Correct extension for WebP file with wrong extension (see #25687, #25717)
+ _, thumbnail_ext = os.path.splitext(thumbnail_filename)
+ if thumbnail_ext:
+ thumbnail_ext = thumbnail_ext[1:].lower()
+ if thumbnail_ext != 'webp' and is_webp(thumbnail_filename):
+ self._downloader.to_screen(
+ '[ffmpeg] Correcting extension to webp and escaping path for thumbnail "%s"' % thumbnail_filename)
+ thumbnail_webp_filename = replace_extension(thumbnail_filename, 'webp')
+ os.rename(encodeFilename(thumbnail_filename), encodeFilename(thumbnail_webp_filename))
+ thumbnail_filename = thumbnail_webp_filename
+ thumbnail_ext = 'webp'
+
+ # Convert unsupported thumbnail formats to JPEG (see #25687, #25717)
+ if thumbnail_ext not in ['jpg', 'png']:
+ # NB: % is supposed to be escaped with %% but this does not work
+ # for input files so working around with standard substitution
+ escaped_thumbnail_filename = thumbnail_filename.replace('%', '#')
+ os.rename(encodeFilename(thumbnail_filename), encodeFilename(escaped_thumbnail_filename))
+ escaped_thumbnail_jpg_filename = replace_extension(escaped_thumbnail_filename, 'jpg')
+ self._downloader.to_screen('[ffmpeg] Converting thumbnail "%s" to JPEG' % escaped_thumbnail_filename)
+ self.run_ffmpeg(escaped_thumbnail_filename, escaped_thumbnail_jpg_filename, ['-bsf:v', 'mjpeg2jpeg'])
+ os.remove(encodeFilename(escaped_thumbnail_filename))
+ thumbnail_jpg_filename = replace_extension(thumbnail_filename, 'jpg')
+ # Rename back to unescaped for further processing
+ os.rename(encodeFilename(escaped_thumbnail_jpg_filename), encodeFilename(thumbnail_jpg_filename))
+ thumbnail_filename = thumbnail_jpg_filename
+
if info['ext'] == 'mp3':
options = [
'-c', 'copy', '-map', '0', '-map', '1',
@@ -55,10 +92,14 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
os.rename(encodeFilename(temp_filename), encodeFilename(filename))
elif info['ext'] in ['m4a', 'mp4']:
- if not check_executable('AtomicParsley', ['-v']):
+ atomicparsley = next((x
+ for x in ['AtomicParsley', 'atomicparsley']
+ if check_executable(x, ['-v'])), None)
+
+ if atomicparsley is None:
raise EmbedThumbnailPPError('AtomicParsley was not found. Please install.')
- cmd = [encodeFilename('AtomicParsley', True),
+ cmd = [encodeFilename(atomicparsley, True),
encodeFilename(filename, True),
encodeArgument('--artwork'),
encodeFilename(thumbnail_filename, True),
@@ -71,7 +112,7 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
self._downloader.to_screen('[debug] AtomicParsley command line: %s' % shell_quote(cmd))
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- stdout, stderr = p.communicate()
+ stdout, stderr = process_communicate_or_kill(p)
if p.returncode != 0:
msg = stderr.decode('utf-8', 'replace').strip()
diff --git a/youtube_dl/postprocessor/execafterdownload.py b/youtube_dl/postprocessor/execafterdownload.py
index 90630c2d7..64dabe790 100644
--- a/youtube_dl/postprocessor/execafterdownload.py
+++ b/youtube_dl/postprocessor/execafterdownload.py
@@ -4,7 +4,10 @@ import subprocess
from .common import PostProcessor
from ..compat import compat_shlex_quote
-from ..utils import PostProcessingError
+from ..utils import (
+ encodeArgument,
+ PostProcessingError,
+)
class ExecAfterDownloadPP(PostProcessor):
@@ -20,7 +23,7 @@ class ExecAfterDownloadPP(PostProcessor):
cmd = cmd.replace('{}', compat_shlex_quote(information['filepath']))
self._downloader.to_screen('[exec] Executing command: %s' % cmd)
- retCode = subprocess.call(cmd, shell=True)
+ retCode = subprocess.call(encodeArgument(cmd), shell=True)
if retCode != 0:
raise PostProcessingError(
'Command returned error code %d' % retCode)
diff --git a/youtube_dl/postprocessor/ffmpeg.py b/youtube_dl/postprocessor/ffmpeg.py
index 665109558..214825aa9 100644
--- a/youtube_dl/postprocessor/ffmpeg.py
+++ b/youtube_dl/postprocessor/ffmpeg.py
@@ -1,16 +1,14 @@
from __future__ import unicode_literals
-import io
import os
import subprocess
import time
+import re
from .common import AudioConversionError, PostProcessor
-from ..compat import (
- compat_subprocess_get_DEVNULL,
-)
+from ..compat import compat_open as open
from ..utils import (
encodeArgument,
encodeFilename,
@@ -18,10 +16,12 @@ from ..utils import (
is_outdated_version,
PostProcessingError,
prepend_extension,
+ process_communicate_or_kill,
shell_quote,
subtitles_filename,
dfxp2srt,
ISO639Utils,
+ replace_extension,
)
@@ -42,7 +42,7 @@ ACODECS = {
'aac': 'aac',
'flac': 'flac',
'm4a': 'aac',
- 'opus': 'opus',
+ 'opus': 'libopus',
'vorbis': 'libvorbis',
'wav': None,
}
@@ -74,16 +74,34 @@ class FFmpegPostProcessor(PostProcessor):
return FFmpegPostProcessor(downloader)._versions
def _determine_executables(self):
- programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe']
- prefer_ffmpeg = False
+ # ordered to match prefer_ffmpeg!
+ convs = ['ffmpeg', 'avconv']
+ probes = ['ffprobe', 'avprobe']
+ prefer_ffmpeg = True
+ programs = convs + probes
+
+ def get_ffmpeg_version(path):
+ ver = get_exe_version(path, args=['-version'])
+ if ver:
+ regexs = [
+ r'(?:\d+:)?([0-9.]+)-[0-9]+ubuntu[0-9.]+$', # Ubuntu, see [1]
+ r'n([0-9.]+)$', # Arch Linux
+ # 1. http://www.ducea.com/2006/06/17/ubuntu-package-version-naming-explanation/
+ ]
+ for regex in regexs:
+ mobj = re.match(regex, ver)
+ if mobj:
+ ver = mobj.group(1)
+ return ver
self.basename = None
self.probe_basename = None
self._paths = None
self._versions = None
+ location = None
if self._downloader:
- prefer_ffmpeg = self._downloader.params.get('prefer_ffmpeg', False)
+ prefer_ffmpeg = self._downloader.params.get('prefer_ffmpeg', True)
location = self._downloader.params.get('ffmpeg_location')
if location is not None:
if not os.path.exists(location):
@@ -104,34 +122,21 @@ class FFmpegPostProcessor(PostProcessor):
location = os.path.dirname(os.path.abspath(location))
if basename in ('ffmpeg', 'ffprobe'):
prefer_ffmpeg = True
-
- self._paths = dict(
- (p, os.path.join(location, p)) for p in programs)
- self._versions = dict(
- (p, get_exe_version(self._paths[p], args=['-version']))
- for p in programs)
- if self._versions is None:
- self._versions = dict(
- (p, get_exe_version(p, args=['-version'])) for p in programs)
- self._paths = dict((p, p) for p in programs)
-
- if prefer_ffmpeg:
- prefs = ('ffmpeg', 'avconv')
- else:
- prefs = ('avconv', 'ffmpeg')
- for p in prefs:
- if self._versions[p]:
- self.basename = p
- break
-
- if prefer_ffmpeg:
- prefs = ('ffprobe', 'avprobe')
- else:
- prefs = ('avprobe', 'ffprobe')
- for p in prefs:
- if self._versions[p]:
- self.probe_basename = p
- break
+ self._paths = dict(
+ (p, p if location is None else os.path.join(location, p))
+ for p in programs)
+ self._versions = dict(
+ x for x in (
+ (p, get_ffmpeg_version(self._paths[p])) for p in programs)
+ if x[1] is not None)
+
+ basenames = [None, None]
+ for i, progs in enumerate((convs, probes)):
+ for p in progs[::-1 if prefer_ffmpeg is False else 1]:
+ if self._versions.get(p):
+ basenames[i] = p
+ break
+ self.basename, self.probe_basename = basenames
@property
def available(self):
@@ -150,27 +155,45 @@ class FFmpegPostProcessor(PostProcessor):
return self._paths[self.probe_basename]
def get_audio_codec(self, path):
- if not self.probe_available:
- raise PostProcessingError('ffprobe or avprobe not found. Please install one.')
+ if not self.probe_available and not self.available:
+ raise PostProcessingError('ffprobe/avprobe and ffmpeg/avconv not found. Please install one.')
try:
- cmd = [
- encodeFilename(self.probe_executable, True),
- encodeArgument('-show_streams'),
- encodeFilename(self._ffmpeg_filename_argument(path), True)]
+ if self.probe_available:
+ cmd = [
+ encodeFilename(self.probe_executable, True),
+ encodeArgument('-show_streams')]
+ else:
+ cmd = [
+ encodeFilename(self.executable, True),
+ encodeArgument('-i')]
+ cmd.append(encodeFilename(self._ffmpeg_filename_argument(path), True))
if self._downloader.params.get('verbose', False):
- self._downloader.to_screen('[debug] %s command line: %s' % (self.basename, shell_quote(cmd)))
- handle = subprocess.Popen(cmd, stderr=compat_subprocess_get_DEVNULL(), stdout=subprocess.PIPE, stdin=subprocess.PIPE)
- output = handle.communicate()[0]
- if handle.wait() != 0:
+ self._downloader.to_screen(
+ '[debug] %s command line: %s' % (self.basename, shell_quote(cmd)))
+ handle = subprocess.Popen(
+ cmd, stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE, stdin=subprocess.PIPE)
+ stdout_data, stderr_data = process_communicate_or_kill(handle)
+ expected_ret = 0 if self.probe_available else 1
+ if handle.wait() != expected_ret:
return None
except (IOError, OSError):
return None
- audio_codec = None
- for line in output.decode('ascii', 'ignore').split('\n'):
- if line.startswith('codec_name='):
- audio_codec = line.split('=')[1].strip()
- elif line.strip() == 'codec_type=audio' and audio_codec is not None:
- return audio_codec
+ output = (stdout_data if self.probe_available else stderr_data).decode('ascii', 'ignore')
+ if self.probe_available:
+ audio_codec = None
+ for line in output.split('\n'):
+ if line.startswith('codec_name='):
+ audio_codec = line.split('=')[1].strip()
+ elif line.strip() == 'codec_type=audio' and audio_codec is not None:
+ return audio_codec
+ else:
+ # Stream #FILE_INDEX:STREAM_INDEX[STREAM_ID](LANGUAGE): CODEC_TYPE: CODEC_NAME
+ mobj = re.search(
+ r'Stream\s*#\d+:\d+(?:\[0x[0-9a-f]+\])?(?:\([a-z]{3}\))?:\s*Audio:\s*([0-9a-z]+)',
+ output)
+ if mobj:
+ return mobj.group(1)
return None
def run_ffmpeg_multiple_files(self, input_paths, out_path, opts):
@@ -187,18 +210,24 @@ class FFmpegPostProcessor(PostProcessor):
encodeArgument('-i'),
encodeFilename(self._ffmpeg_filename_argument(path), True)
])
- cmd = ([encodeFilename(self.executable, True), encodeArgument('-y')] +
- files_cmd +
- [encodeArgument(o) for o in opts] +
- [encodeFilename(self._ffmpeg_filename_argument(out_path), True)])
+ cmd = [encodeFilename(self.executable, True), encodeArgument('-y')]
+ # avconv does not have repeat option
+ if self.basename == 'ffmpeg':
+ cmd += [encodeArgument('-loglevel'), encodeArgument('repeat+info')]
+ cmd += (files_cmd
+ + [encodeArgument(o) for o in opts]
+ + [encodeFilename(self._ffmpeg_filename_argument(out_path), True)])
if self._downloader.params.get('verbose', False):
self._downloader.to_screen('[debug] ffmpeg command line: %s' % shell_quote(cmd))
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
- stdout, stderr = p.communicate()
+ stdout, stderr = process_communicate_or_kill(p)
if p.returncode != 0:
stderr = stderr.decode('utf-8', 'replace')
- msg = stderr.strip().split('\n')[-1]
+ msgs = stderr.strip().split('\n')
+ msg = msgs[-1]
+ if self._downloader.params.get('verbose', False):
+ self._downloader.to_screen('[debug] ' + '\n'.join(msgs[:-1]))
raise FFmpegPostProcessorError(msg)
self.try_utime(out_path, oldest_mtime, oldest_mtime)
@@ -293,8 +322,8 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
information['ext'] = extension
# If we download foo.mp3 and convert it to... foo.mp3, then don't delete foo.mp3, silly.
- if (new_path == path or
- (self._nopostoverwrites and os.path.exists(encodeFilename(new_path)))):
+ if (new_path == path
+ or (self._nopostoverwrites and os.path.exists(encodeFilename(new_path)))):
self._downloader.to_screen('[ffmpeg] Post-process file %s exists, skipping' % new_path)
return [], information
@@ -360,7 +389,7 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor):
sub_ext = sub_info['ext']
if ext != 'webm' or ext == 'webm' and sub_ext == 'vtt':
sub_langs.append(lang)
- sub_filenames.append(subtitles_filename(filename, lang, sub_ext))
+ sub_filenames.append(subtitles_filename(filename, lang, sub_ext, ext))
else:
if not webm_vtt_warn and ext == 'webm' and sub_ext != 'vtt':
webm_vtt_warn = True
@@ -377,14 +406,16 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor):
# Don't copy the existing subtitles, we may be running the
# postprocessor a second time
'-map', '-0:s',
+ # Don't copy Apple TV chapters track, bin_data (see #19042, #19024,
+ # https://trac.ffmpeg.org/ticket/6016)
+ '-map', '-0:d',
]
if information['ext'] == 'mp4':
opts += ['-c:s', 'mov_text']
for (i, lang) in enumerate(sub_langs):
opts.extend(['-map', '%d:0' % (i + 1)])
- lang_code = ISO639Utils.short2long(lang)
- if lang_code is not None:
- opts.extend(['-metadata:s:s:%d' % i, 'language=%s' % lang_code])
+ lang_code = ISO639Utils.short2long(lang) or lang
+ opts.extend(['-metadata:s:s:%d' % i, 'language=%s' % lang_code])
temp_filename = prepend_extension(filename, 'temp')
self._downloader.to_screen('[ffmpeg] Embedding subtitles in \'%s\'' % filename)
@@ -412,6 +443,13 @@ class FFmpegMetadataPP(FFmpegPostProcessor):
metadata[meta_f] = info[info_f]
break
+ # See [1-4] for some info on media metadata/metadata supported
+ # by ffmpeg.
+ # 1. https://kdenlive.org/en/project/adding-meta-data-to-mp4-video/
+ # 2. https://wiki.multimedia.cx/index.php/FFmpeg_Metadata
+ # 3. https://kodi.wiki/view/Video_file_tagging
+ # 4. http://atomicparsley.sourceforge.net/mpeg-4files.html
+
add('title', ('track', 'title'))
add('date', 'upload_date')
add(('description', 'comment'), 'description')
@@ -422,6 +460,10 @@ class FFmpegMetadataPP(FFmpegPostProcessor):
add('album')
add('album_artist')
add('disc', 'disc_number')
+ add('show', 'series')
+ add('season_number')
+ add('episode_id', ('episode', 'episode_id'))
+ add('episode_sort', 'episode_number')
if not metadata:
self._downloader.to_screen('[ffmpeg] There isn\'t any metadata to add')
@@ -429,17 +471,40 @@ class FFmpegMetadataPP(FFmpegPostProcessor):
filename = info['filepath']
temp_filename = prepend_extension(filename, 'temp')
+ in_filenames = [filename]
+ options = []
if info['ext'] == 'm4a':
- options = ['-vn', '-acodec', 'copy']
+ options.extend(['-vn', '-acodec', 'copy'])
else:
- options = ['-c', 'copy']
+ options.extend(['-c', 'copy'])
for (name, value) in metadata.items():
options.extend(['-metadata', '%s=%s' % (name, value)])
+ chapters = info.get('chapters', [])
+ if chapters:
+ metadata_filename = replace_extension(filename, 'meta')
+ with open(metadata_filename, 'w', encoding='utf-8') as f:
+ def ffmpeg_escape(text):
+ return re.sub(r'(=|;|#|\\|\n)', r'\\\1', text)
+
+ metadata_file_content = ';FFMETADATA1\n'
+ for chapter in chapters:
+ metadata_file_content += '[CHAPTER]\nTIMEBASE=1/1000\n'
+ metadata_file_content += 'START=%d\n' % (chapter['start_time'] * 1000)
+ metadata_file_content += 'END=%d\n' % (chapter['end_time'] * 1000)
+ chapter_title = chapter.get('title')
+ if chapter_title:
+ metadata_file_content += 'title=%s\n' % ffmpeg_escape(chapter_title)
+ f.write(metadata_file_content)
+ in_filenames.append(metadata_filename)
+ options.extend(['-map_metadata', '1'])
+
self._downloader.to_screen('[ffmpeg] Adding metadata to \'%s\'' % filename)
- self.run_ffmpeg(filename, temp_filename, options)
+ self.run_ffmpeg_multiple_files(in_filenames, temp_filename, options)
+ if chapters:
+ os.remove(metadata_filename)
os.remove(encodeFilename(filename))
os.rename(encodeFilename(temp_filename), encodeFilename(filename))
return [], info
@@ -517,7 +582,7 @@ class FFmpegFixupM3u8PP(FFmpegPostProcessor):
temp_filename = prepend_extension(filename, 'temp')
options = ['-c', 'copy', '-f', 'mp4', '-bsf:a', 'aac_adtstoasc']
- self._downloader.to_screen('[ffmpeg] Fixing malformated aac bitstream in "%s"' % filename)
+ self._downloader.to_screen('[ffmpeg] Fixing malformed AAC bitstream in "%s"' % filename)
self.run_ffmpeg(filename, temp_filename, options)
os.remove(encodeFilename(filename))
@@ -548,9 +613,9 @@ class FFmpegSubtitlesConvertorPP(FFmpegPostProcessor):
self._downloader.to_screen(
'[ffmpeg] Subtitle file for %s is already in the requested format' % new_ext)
continue
- old_file = subtitles_filename(filename, lang, ext)
+ old_file = subtitles_filename(filename, lang, ext, info.get('ext'))
sub_filenames.append(old_file)
- new_file = subtitles_filename(filename, lang, new_ext)
+ new_file = subtitles_filename(filename, lang, new_ext, info.get('ext'))
if ext in ('dfxp', 'ttml', 'tt'):
self._downloader.report_warning(
@@ -558,12 +623,12 @@ class FFmpegSubtitlesConvertorPP(FFmpegPostProcessor):
'which results in style information loss')
dfxp_file = old_file
- srt_file = subtitles_filename(filename, lang, 'srt')
+ srt_file = subtitles_filename(filename, lang, 'srt', info.get('ext'))
- with io.open(dfxp_file, 'rt', encoding='utf-8') as f:
+ with open(dfxp_file, 'rb') as f:
srt_data = dfxp2srt(f.read())
- with io.open(srt_file, 'wt', encoding='utf-8') as f:
+ with open(srt_file, 'w', encoding='utf-8') as f:
f.write(srt_data)
old_file = srt_file
@@ -579,7 +644,7 @@ class FFmpegSubtitlesConvertorPP(FFmpegPostProcessor):
self.run_ffmpeg(old_file, new_file, ['-f', new_format])
- with io.open(new_file, 'rt', encoding='utf-8') as f:
+ with open(new_file, 'r', encoding='utf-8') as f:
subs[lang] = {
'ext': new_ext,
'data': f.read(),
diff --git a/youtube_dl/postprocessor/metadatafromtitle.py b/youtube_dl/postprocessor/metadatafromtitle.py
index a7d637a3c..6cd5bb70f 100644
--- a/youtube_dl/postprocessor/metadatafromtitle.py
+++ b/youtube_dl/postprocessor/metadatafromtitle.py
@@ -9,7 +9,9 @@ class MetadataFromTitlePP(PostProcessor):
def __init__(self, downloader, titleformat):
super(MetadataFromTitlePP, self).__init__(downloader)
self._titleformat = titleformat
- self._titleregex = self.format_to_regex(titleformat)
+ self._titleregex = (self.format_to_regex(titleformat)
+ if re.search(r'%\(\w+\)s', titleformat)
+ else titleformat)
def format_to_regex(self, fmt):
r"""
@@ -33,11 +35,16 @@ class MetadataFromTitlePP(PostProcessor):
title = info['title']
match = re.match(self._titleregex, title)
if match is None:
- self._downloader.to_screen('[fromtitle] Could not interpret title of video as "%s"' % self._titleformat)
+ self._downloader.to_screen(
+ '[fromtitle] Could not interpret title of video as "%s"'
+ % self._titleformat)
return [], info
for attribute, value in match.groupdict().items():
- value = match.group(attribute)
+ if value is None:
+ continue
info[attribute] = value
- self._downloader.to_screen('[fromtitle] parsed ' + attribute + ': ' + value)
+ self._downloader.to_screen(
+ '[fromtitle] parsed %s: %s'
+ % (attribute, value if value is not None else 'NA'))
return [], info
diff --git a/youtube_dl/postprocessor/xattrpp.py b/youtube_dl/postprocessor/xattrpp.py
index fbdfa02ac..814dabecf 100644
--- a/youtube_dl/postprocessor/xattrpp.py
+++ b/youtube_dl/postprocessor/xattrpp.py
@@ -42,6 +42,7 @@ class XAttrMetadataPP(PostProcessor):
'user.dublincore.format': 'format',
}
+ num_written = 0
for xattrname, infoname in xattr_mapping.items():
value = info.get(infoname)
@@ -52,6 +53,7 @@ class XAttrMetadataPP(PostProcessor):
byte_value = value.encode('utf-8')
write_xattr(filename, xattrname, byte_value)
+ num_written += 1
return [], info
@@ -62,8 +64,8 @@ class XAttrMetadataPP(PostProcessor):
except XAttrMetadataError as e:
if e.reason == 'NO_SPACE':
self._downloader.report_warning(
- 'There\'s no disk space left or disk quota exceeded. ' +
- 'Extended attributes are not written.')
+ 'There\'s no disk space left, disk quota exceeded or filesystem xattr limit exceeded. '
+ + (('Some ' if num_written else '') + 'extended attributes are not written.').capitalize())
elif e.reason == 'VALUE_TOO_LONG':
self._downloader.report_warning(
'Unable to write extended attributes due to too long values.')