1 from __future__
import unicode_literals
7 from ..compat
import compat_str
15 class PostProcessorMetaClass(type):
17 def run_wrapper(func
):
18 @functools.wraps(func
)
19 def run(self
, info
, *args
, **kwargs
):
20 self
._hook
_progress
({'status': 'started'}
, info
)
21 ret
= func(self
, info
, *args
, **kwargs
)
24 self
._hook
_progress
({'status': 'finished'}
, info
)
28 def __new__(cls
, name
, bases
, attrs
):
30 attrs
['run'] = cls
.run_wrapper(attrs
['run'])
31 return type.__new
__(cls
, name
, bases
, attrs
)
34 class PostProcessor(metaclass
=PostProcessorMetaClass
):
35 """Post Processor class.
37 PostProcessor objects can be added to downloaders with their
38 add_post_processor() method. When the downloader has finished a
39 successful download, it will take its internal chain of PostProcessors
40 and start calling the run() method on each one of them, first with
41 an initial argument and then with the returned value of the previous
44 The chain will be stopped if one of them ever returns None or the end
45 of the chain is reached.
47 PostProcessor objects follow a "mutual registration" process similar
48 to InfoExtractor objects.
50 Optionally PostProcessor can use a list of additional command-line arguments
51 with self._configuration_args.
56 def __init__(self
, downloader
=None):
57 self
._progress
_hooks
= []
58 self
.add_progress_hook(self
.report_progress
)
59 self
.set_downloader(downloader
)
60 self
.PP_NAME
= self
.pp_key()
64 name
= cls
.__name
__[:-2]
65 return compat_str(name
[6:]) if name
[:6].lower() == 'ffmpeg' else name
67 def to_screen(self
, text
, prefix
=True, *args
, **kwargs
):
68 tag
= '[%s] ' % self
.PP_NAME
if prefix
else ''
70 return self
._downloader
.to_screen('%s%s' % (tag
, text
), *args
, **kwargs
)
72 def report_warning(self
, text
, *args
, **kwargs
):
74 return self
._downloader
.report_warning(text
, *args
, **kwargs
)
76 def report_error(self
, text
, *args
, **kwargs
):
77 # Exists only for compatibility. Do not use
79 return self
._downloader
.report_error(text
, *args
, **kwargs
)
81 def write_debug(self
, text
, *args
, **kwargs
):
83 return self
._downloader
.write_debug(text
, *args
, **kwargs
)
85 def get_param(self
, name
, default
=None, *args
, **kwargs
):
87 return self
._downloader
.params
.get(name
, default
, *args
, **kwargs
)
90 def set_downloader(self
, downloader
):
91 """Sets the downloader for this PP."""
92 self
._downloader
= downloader
95 for ph
in downloader
._postprocessor
_hooks
:
96 self
.add_progress_hook(ph
)
99 def _restrict_to(*, video
=True, audio
=True, images
=True):
100 allowed
= {'video': video, 'audio': audio, 'images': images}
103 @functools.wraps(func
)
104 def wrapper(self
, info
):
106 'video' if info
.get('vcodec') != 'none'
107 else 'audio' if info
.get('acodec') != 'none'
109 if allowed
[format_type
]:
110 return func(self
, info
)
112 self
.to_screen('Skipping %s' % format_type
)
117 def run(self
, information
):
118 """Run the PostProcessor.
120 The "information" argument is a dictionary like the ones
121 composed by InfoExtractors. The only difference is that this
122 one has an extra field called "filepath" that points to the
125 This method returns a tuple, the first element is a list of the files
126 that can be deleted, and the second of which is the updated
129 In addition, this method may raise a PostProcessingError
130 exception if post processing fails.
132 return [], information
# by default, keep file and do nothing
134 def try_utime(self
, path
, atime
, mtime
, errnote
='Cannot update utime of file'):
136 os
.utime(encodeFilename(path
), (atime
, mtime
))
138 self
.report_warning(errnote
)
140 def _configuration_args(self
, exe
, *args
, **kwargs
):
141 return _configuration_args(
142 self
.pp_key(), self
.get_param('postprocessor_args'), exe
, *args
, **kwargs
)
144 def _hook_progress(self
, status
, info_dict
):
145 if not self
._progress
_hooks
:
147 info_dict
= dict(info_dict
)
148 for key
in ('__original_infodict', '__postprocessors'):
149 info_dict
.pop(key
, None)
151 'info_dict': copy
.deepcopy(info_dict
),
152 'postprocessor': self
.pp_key(),
154 for ph
in self
._progress
_hooks
:
157 def add_progress_hook(self
, ph
):
158 # See YoutubeDl.py (search for postprocessor_hooks) for a description of this interface
159 self
._progress
_hooks
.append(ph
)
161 def report_progress(self
, s
):
162 s
['_default_template'] = '%(postprocessor)s %(status)s' % s
164 progress_dict
= s
.copy()
165 progress_dict
.pop('info_dict')
166 progress_dict
= {'info': s['info_dict'], 'progress': progress_dict}
168 progress_template
= self
.get_param('progress_template', {})
169 tmpl
= progress_template
.get('postprocess')
171 self
._downloader
.to_stdout(self
._downloader
.evaluate_outtmpl(tmpl
, progress_dict
))
173 self
._downloader
.to_console_title(self
._downloader
.evaluate_outtmpl(
174 progress_template
.get('postprocess-title') or 'yt-dlp %(progress._default_template)s',
178 class AudioConversionError(PostProcessingError
):