]> jfr.im git - yt-dlp.git/commitdiff
Multiple output templates for different file types
authorpukkandan <redacted>
Wed, 3 Feb 2021 13:36:09 +0000 (19:06 +0530)
committerpukkandan <redacted>
Thu, 4 Feb 2021 22:41:39 +0000 (04:11 +0530)
Syntax: -o common_template -o type:type_template
Types supported: subtitle|thumbnail|description|annotation|infojson|pl_description|pl_infojson

README.md
youtube_dlc/YoutubeDL.py
youtube_dlc/__init__.py
youtube_dlc/options.py
youtube_dlc/postprocessor/embedthumbnail.py
youtube_dlc/postprocessor/ffmpeg.py
youtube_dlc/postprocessor/movefilesafterdownload.py
youtube_dlc/utils.py

index 2ff137e45074f674db240c8a00c89ae519d5d45a..60d32d6e31e8ec2fb72f28570c579d33c08e4af5 100644 (file)
--- a/README.md
+++ b/README.md
@@ -333,16 +333,16 @@ ## Filesystem Options:
                                      comments and ignored
     -P, --paths TYPE:PATH            The paths where the files should be
                                      downloaded. Specify the type of file and
-                                     the path separated by a colon ":"
-                                     (supported: description|annotation|subtitle
-                                     |infojson|thumbnail). Additionally, you can
-                                     also provide "home" and "temp" paths. All
-                                     intermediary files are first downloaded to
-                                     the temp path and then the final files are
-                                     moved over to the home path after download
-                                     is finished. Note that this option is
-                                     ignored if --output is an absolute path
-    -o, --output TEMPLATE            Output filename template, see "OUTPUT
+                                     the path separated by a colon ":". All the
+                                     same types as --output are supported.
+                                     Additionally, you can also provide "home"
+                                     and "temp" paths. All intermediary files
+                                     are first downloaded to the temp path and
+                                     then the final files are moved over to the
+                                     home path after download is finished. This
+                                     option is ignored if --output is an
+                                     absolute path
+    -o, --output [TYPE:]TEMPLATE     Output filename template, see "OUTPUT
                                      TEMPLATE" for details
     --output-na-placeholder TEXT     Placeholder value for unavailable meta
                                      fields in output filename template
@@ -751,7 +751,9 @@ # OUTPUT TEMPLATE
 
 **tl;dr:** [navigate me to examples](#output-template-examples).
 
-The basic usage of `-o` is not to set any template arguments when downloading a single file, like in `youtube-dlc -o funny_video.flv "https://some/video"`. However, it may contain special sequences that will be replaced when downloading each video. The special sequences may be formatted according to [python string formatting operations](https://docs.python.org/2/library/stdtypes.html#string-formatting). For example, `%(NAME)s` or `%(NAME)05d`. To clarify, that is a percent symbol followed by a name in parentheses, followed by formatting operations. Additionally, date/time fields can be formatted according to [strftime formatting](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) by specifying it inside the parantheses seperated from the field name using a `>`. For example, `%(duration>%H-%M-%S)s`.
+The basic usage of `-o` is not to set any template arguments when downloading a single file, like in `youtube-dlc -o funny_video.flv "https://some/video"`. However, it may contain special sequences that will be replaced when downloading each video. The special sequences may be formatted according to [python string formatting operations](https://docs.python.org/2/library/stdtypes.html#string-formatting). For example, `%(NAME)s` or `%(NAME)05d`. To clarify, that is a percent symbol followed by a name in parentheses, followed by formatting operations. Date/time fields can also be formatted according to [strftime formatting](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) by specifying it inside the parantheses seperated from the field name using a `>`. For example, `%(duration>%H-%M-%S)s`.
+
+Additionally, you can set different output templates for the various metadata files seperately from the general output template by specifying the type of file followed by the template seperated by a colon ":". The different filetypes supported are subtitle|thumbnail|description|annotation|infojson|pl_description|pl_infojson. For example, `-o '%(title)s.%(ext)s' -o 'thumbnail:%(title)s\%(title)s.%(ext)s'`  will put the thumbnails in a folder with the same name as the video.
 
 The available fields are:
 
@@ -860,7 +862,7 @@ #### Output template and Windows batch files
 
 #### Output template examples
 
-Note that on Windows you may need to use double quotes instead of single.
+Note that on Windows you need to use double quotes instead of single.
 
 ```bash
 $ youtube-dlc --get-filename -o '%(title)s.%(ext)s' BaW_jenozKc
index da5001f075063c5b0000275657cfa57d44ab50cf..9631745defbc466ef361e67d49d622b9e147a6c7 100644 (file)
@@ -49,6 +49,7 @@
     date_from_str,
     DateRange,
     DEFAULT_OUTTMPL,
+    OUTTMPL_TYPES,
     determine_ext,
     determine_protocol,
     DOT_DESKTOP_LINK_TEMPLATE,
@@ -182,7 +183,8 @@ class YoutubeDL(object):
     format_sort_force: Force the given format_sort. see "Sorting Formats" for more details.
     allow_multiple_video_streams:   Allow multiple video streams to be merged into a single file
     allow_multiple_audio_streams:   Allow multiple audio streams to be merged into a single file
-    outtmpl:           Template for output names.
+    outtmpl:           Dictionary of templates for output names. Allowed keys
+                       are 'default' and the keys of OUTTMPL_TYPES (in utils.py)
     outtmpl_na_placeholder: Placeholder for unavailable meta fields.
     restrictfilenames: Do not allow "&" and spaces in file names
     trim_file_name:    Limit length of filename (extension excluded)
@@ -493,10 +495,7 @@ def check_deprecated(param, option, suggestion):
                 'Set the LC_ALL environment variable to fix this.')
             self.params['restrictfilenames'] = True
 
-        if isinstance(params.get('outtmpl'), bytes):
-            self.report_warning(
-                'Parameter outtmpl is bytes, but should be a unicode string. '
-                'Put  from __future__ import unicode_literals  at the top of your code file or consider switching to Python 3.x.')
+        self.outtmpl_dict = self.parse_outtmpl()
 
         self._setup_opener()
 
@@ -732,8 +731,21 @@ def report_file_delete(self, file_name):
         except UnicodeEncodeError:
             self.to_screen('Deleting already existent file')
 
-    def prepare_filename(self, info_dict, warn=False):
-        """Generate the output filename."""
+    def parse_outtmpl(self):
+        outtmpl_dict = self.params.get('outtmpl', {})
+        if not isinstance(outtmpl_dict, dict):
+            outtmpl_dict = {'default': outtmpl_dict}
+        outtmpl_dict.update({
+            k: v for k, v in DEFAULT_OUTTMPL.items()
+            if not outtmpl_dict.get(k)})
+        for key, val in outtmpl_dict.items():
+            if isinstance(val, bytes):
+                self.report_warning(
+                    'Parameter outtmpl is bytes, but should be a unicode string. '
+                    'Put  from __future__ import unicode_literals  at the top of your code file or consider switching to Python 3.x.')
+        return outtmpl_dict
+
+    def _prepare_filename(self, info_dict, tmpl_type='default'):
         try:
             template_dict = dict(info_dict)
 
@@ -765,7 +777,8 @@ def prepare_filename(self, info_dict, warn=False):
             na = self.params.get('outtmpl_na_placeholder', 'NA')
             template_dict = collections.defaultdict(lambda: na, template_dict)
 
-            outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
+            outtmpl = self.outtmpl_dict.get(tmpl_type, self.outtmpl_dict['default'])
+            force_ext = OUTTMPL_TYPES.get(tmpl_type)
 
             # For fields playlist_index and autonumber convert all occurrences
             # of %(field)s to %(field)0Nd for backward compatibility
@@ -835,6 +848,9 @@ def prepare_filename(self, info_dict, warn=False):
             # title "Hello $PATH", we don't want `$PATH` to be expanded.
             filename = expand_path(outtmpl).replace(sep, '') % template_dict
 
+            if force_ext is not None:
+                filename = replace_extension(filename, force_ext, template_dict.get('ext'))
+
             # https://github.com/blackjack4494/youtube-dlc/issues/85
             trim_file_name = self.params.get('trim_file_name', False)
             if trim_file_name:
@@ -852,25 +868,28 @@ def prepare_filename(self, info_dict, warn=False):
                 filename = encodeFilename(filename, True).decode(preferredencoding())
             filename = sanitize_path(filename)
 
-            if warn and not self.__prepare_filename_warned:
-                if not self.params.get('paths'):
-                    pass
-                elif filename == '-':
-                    self.report_warning('--paths is ignored when an outputting to stdout')
-                elif os.path.isabs(filename):
-                    self.report_warning('--paths is ignored since an absolute path is given in output template')
-                self.__prepare_filename_warned = True
-
             return filename
         except ValueError as err:
             self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
             return None
 
-    def prepare_filepath(self, filename, dir_type=''):
-        if filename == '-':
-            return filename
+    def prepare_filename(self, info_dict, dir_type='', warn=False):
+        """Generate the output filename."""
         paths = self.params.get('paths', {})
         assert isinstance(paths, dict)
+        filename = self._prepare_filename(info_dict, dir_type or 'default')
+
+        if warn and not self.__prepare_filename_warned:
+            if not paths:
+                pass
+            elif filename == '-':
+                self.report_warning('--paths is ignored when an outputting to stdout')
+            elif os.path.isabs(filename):
+                self.report_warning('--paths is ignored since an absolute path is given in output template')
+            self.__prepare_filename_warned = True
+        if filename == '-' or not filename:
+            return filename
+
         homepath = expand_path(paths.get('home', '').strip())
         assert isinstance(homepath, compat_str)
         subdir = expand_path(paths.get(dir_type, '').strip()) if dir_type else ''
@@ -1041,10 +1060,7 @@ def process_ie_result(self, ie_result, download=True, extra_info={}):
             extract_flat = self.params.get('extract_flat', False)
             if ((extract_flat == 'in_playlist' and 'playlist' in extra_info)
                     or extract_flat is True):
-                self.__forced_printings(
-                    ie_result,
-                    self.prepare_filepath(self.prepare_filename(ie_result)),
-                    incomplete=True)
+                self.__forced_printings(ie_result, self.prepare_filename(ie_result), incomplete=True)
                 return ie_result
 
         if result_type == 'video':
@@ -1150,9 +1166,7 @@ def ensure_dir_exists(path):
                 return make_dir(path, self.report_error)
 
             if self.params.get('writeinfojson', False):
-                infofn = replace_extension(
-                    self.prepare_filepath(self.prepare_filename(ie_copy), 'infojson'),
-                    'info.json', ie_result.get('ext'))
+                infofn = self.prepare_filename(ie_copy, 'pl_infojson')
                 if not ensure_dir_exists(encodeFilename(infofn)):
                     return
                 if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(infofn)):
@@ -1168,9 +1182,7 @@ def ensure_dir_exists(path):
                         self.report_error('Cannot write playlist metadata to JSON file ' + infofn)
 
             if self.params.get('writedescription', False):
-                descfn = replace_extension(
-                    self.prepare_filepath(self.prepare_filename(ie_copy), 'description'),
-                    'description', ie_result.get('ext'))
+                descfn = self.prepare_filename(ie_copy, 'pl_description')
                 if not ensure_dir_exists(encodeFilename(descfn)):
                     return
                 if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(descfn)):
@@ -1370,7 +1382,7 @@ def can_merge():
             and (
                 not can_merge()
                 or info_dict.get('is_live', False)
-                or self.params.get('outtmpl', DEFAULT_OUTTMPL) == '-'))
+                or self.outtmpl_dict['default'] == '-'))
 
         return (
             'best/bestvideo+bestaudio'
@@ -2032,10 +2044,10 @@ def process_info(self, info_dict):
 
         info_dict = self.pre_process(info_dict)
 
-        filename = self.prepare_filename(info_dict, warn=True)
-        info_dict['_filename'] = full_filename = self.prepare_filepath(filename)
-        temp_filename = self.prepare_filepath(filename, 'temp')
+        info_dict['_filename'] = full_filename = self.prepare_filename(info_dict, warn=True)
+        temp_filename = self.prepare_filename(info_dict, 'temp')
         files_to_move = {}
+        skip_dl = self.params.get('skip_download', False)
 
         # Forced printings
         self.__forced_printings(info_dict, full_filename, incomplete=False)
@@ -2047,7 +2059,7 @@ def process_info(self, info_dict):
             # Do nothing else if in simulate mode
             return
 
-        if filename is None:
+        if full_filename is None:
             return
 
         def ensure_dir_exists(path):
@@ -2059,9 +2071,7 @@ def ensure_dir_exists(path):
             return
 
         if self.params.get('writedescription', False):
-            descfn = replace_extension(
-                self.prepare_filepath(filename, 'description'),
-                'description', info_dict.get('ext'))
+            descfn = self.prepare_filename(info_dict, 'description')
             if not ensure_dir_exists(encodeFilename(descfn)):
                 return
             if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(descfn)):
@@ -2078,9 +2088,7 @@ def ensure_dir_exists(path):
                     return
 
         if self.params.get('writeannotations', False):
-            annofn = replace_extension(
-                self.prepare_filepath(filename, 'annotation'),
-                'annotations.xml', info_dict.get('ext'))
+            annofn = self.prepare_filename(info_dict, 'annotation')
             if not ensure_dir_exists(encodeFilename(annofn)):
                 return
             if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(annofn)):
@@ -2116,10 +2124,11 @@ def dl(name, info, subtitle=False):
             # ie = self.get_info_extractor(info_dict['extractor_key'])
             for sub_lang, sub_info in subtitles.items():
                 sub_format = sub_info['ext']
-                sub_filename = subtitles_filename(temp_filename, sub_lang, sub_format, info_dict.get('ext'))
-                sub_filename_final = subtitles_filename(
-                    self.prepare_filepath(filename, 'subtitle'),
+                sub_fn = self.prepare_filename(info_dict, 'subtitle')
+                sub_filename = subtitles_filename(
+                     temp_filename if not skip_dl else sub_fn,
                     sub_lang, sub_format, info_dict.get('ext'))
+                sub_filename_final = subtitles_filename(sub_fn, sub_lang, sub_format, info_dict.get('ext'))
                 if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(sub_filename)):
                     self.to_screen('[info] Video subtitle %s.%s is already present' % (sub_lang, sub_format))
                     files_to_move[sub_filename] = sub_filename_final
@@ -2153,10 +2162,10 @@ def dl(name, info, subtitle=False):
                                                 (sub_lang, error_to_compat_str(err)))
                             continue
 
-        if self.params.get('skip_download', False):
+        if skip_dl:
             if self.params.get('convertsubtitles', False):
                 # subconv = FFmpegSubtitlesConvertorPP(self, format=self.params.get('convertsubtitles'))
-                filename_real_ext = os.path.splitext(filename)[1][1:]
+                filename_real_ext = os.path.splitext(full_filename)[1][1:]
                 filename_wo_ext = (
                     os.path.splitext(full_filename)[0]
                     if filename_real_ext == info_dict['ext']
@@ -2176,9 +2185,7 @@ def dl(name, info, subtitle=False):
                         return
 
         if self.params.get('writeinfojson', False):
-            infofn = replace_extension(
-                self.prepare_filepath(filename, 'infojson'),
-                'info.json', info_dict.get('ext'))
+            infofn = self.prepare_filename(info_dict, 'infojson')
             if not ensure_dir_exists(encodeFilename(infofn)):
                 return
             if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(infofn)):
@@ -2190,11 +2197,14 @@ def dl(name, info, subtitle=False):
                 except (OSError, IOError):
                     self.report_error('Cannot write video metadata to JSON file ' + infofn)
                     return
-            info_dict['__infojson_filepath'] = infofn
+            info_dict['__infojson_filename'] = infofn
 
-        thumbdir = os.path.dirname(self.prepare_filepath(filename, 'thumbnail'))
-        for thumbfn in self._write_thumbnails(info_dict, temp_filename):
-            files_to_move[thumbfn] = os.path.join(thumbdir, os.path.basename(thumbfn))
+        thumbfn = self.prepare_filename(info_dict, 'thumbnail')
+        thumb_fn_temp = temp_filename if not skip_dl else thumbfn
+        for thumb_ext in self._write_thumbnails(info_dict, thumb_fn_temp):
+            thumb_filename_temp = replace_extension(thumb_fn_temp, thumb_ext, info_dict.get('ext'))
+            thumb_filename = replace_extension(thumbfn, thumb_ext, info_dict.get('ext'))
+            files_to_move[thumb_filename_temp] = info_dict['__thumbnail_filename'] = thumb_filename
 
         # Write internet shortcut files
         url_link = webloc_link = desktop_link = False
@@ -2247,7 +2257,7 @@ def _write_link_file(extension, template, newline, embed_filename):
 
         # Download
         must_record_download_archive = False
-        if not self.params.get('skip_download', False):
+        if not skip_dl:
             try:
 
                 def existing_file(*filepaths):
@@ -2327,7 +2337,7 @@ def correct_ext(filename):
                             new_info = dict(info_dict)
                             new_info.update(f)
                             fname = prepend_extension(
-                                self.prepare_filepath(self.prepare_filename(new_info), 'temp'),
+                                self.prepare_filename(new_info, 'temp'),
                                 'f%s' % f['format_id'], new_info['ext'])
                             if not ensure_dir_exists(fname):
                                 return
@@ -2357,7 +2367,7 @@ def correct_ext(filename):
                 self.report_error('content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
                 return
 
-            if success and filename != '-':
+            if success and full_filename != '-':
                 # Fixup content
                 fixup_policy = self.params.get('fixup')
                 if fixup_policy is None:
@@ -2439,7 +2449,7 @@ def correct_ext(filename):
 
     def download(self, url_list):
         """Download a given list of URLs."""
-        outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
+        outtmpl = self.outtmpl_dict['default']
         if (len(url_list) > 1
                 and outtmpl != '-'
                 and '%' not in outtmpl
@@ -2522,12 +2532,13 @@ def post_process(self, filename, ie_info, files_to_move={}):
         """Run all the postprocessors on the given file."""
         info = dict(ie_info)
         info['filepath'] = filename
+        info['__files_to_move'] = {}
 
         for pp in ie_info.get('__postprocessors', []) + self._pps['normal']:
             files_to_move, info = self.run_pp(pp, info, files_to_move)
-        info = self.run_pp(MoveFilesAfterDownloadPP(self, files_to_move), info, files_to_move)[1]
+        info = self.run_pp(MoveFilesAfterDownloadPP(self, files_to_move), info)[1]
         for pp in self._pps['aftermove']:
-            files_to_move, info = self.run_pp(pp, info, {})
+            info = self.run_pp(pp, info, {})[1]
 
     def _make_archive_id(self, info_dict):
         video_id = info_dict.get('id')
@@ -2878,7 +2889,7 @@ def get_encoding(self):
             encoding = preferredencoding()
         return encoding
 
-    def _write_thumbnails(self, info_dict, filename):
+    def _write_thumbnails(self, info_dict, filename):  # return the extensions
         if self.params.get('writethumbnail', False):
             thumbnails = info_dict.get('thumbnails')
             if thumbnails:
@@ -2891,12 +2902,12 @@ def _write_thumbnails(self, info_dict, filename):
         ret = []
         for t in thumbnails:
             thumb_ext = determine_ext(t['url'], 'jpg')
-            suffix = '_%s' % t['id'] if len(thumbnails) > 1 else ''
+            suffix = '%s.' % t['id'] if len(thumbnails) > 1 else ''
             thumb_display_id = '%s ' % t['id'] if len(thumbnails) > 1 else ''
-            t['filename'] = thumb_filename = replace_extension(filename + suffix, thumb_ext, info_dict.get('ext'))
+            t['filename'] = thumb_filename = replace_extension(filename, suffix + thumb_ext, info_dict.get('ext'))
 
             if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(thumb_filename)):
-                ret.append(thumb_filename)
+                ret.append(suffix + thumb_ext)
                 self.to_screen('[%s] %s: Thumbnail %sis already present' %
                                (info_dict['extractor'], info_dict['id'], thumb_display_id))
             else:
@@ -2906,7 +2917,7 @@ def _write_thumbnails(self, info_dict, filename):
                     uf = self.urlopen(t['url'])
                     with open(encodeFilename(thumb_filename), 'wb') as thumbf:
                         shutil.copyfileobj(uf, thumbf)
-                    ret.append(thumb_filename)
+                    ret.append(suffix + thumb_ext)
                     self.to_screen('[%s] %s: Writing thumbnail %sto: %s' %
                                    (info_dict['extractor'], info_dict['id'], thumb_display_id, thumb_filename))
                 except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
index bb94389e5823fb4dbf301a3b56b0d364c540fc52..646b135192be507164d85fd05c92fac8c7785485 100644 (file)
@@ -237,18 +237,21 @@ def parse_retries(retries):
     if opts.allsubtitles and not opts.writeautomaticsub:
         opts.writesubtitles = True
 
-    outtmpl = ((opts.outtmpl is not None and opts.outtmpl)
-               or (opts.format == '-1' and opts.usetitle and '%(title)s-%(id)s-%(format)s.%(ext)s')
-               or (opts.format == '-1' and '%(id)s-%(format)s.%(ext)s')
-               or (opts.usetitle and opts.autonumber and '%(autonumber)s-%(title)s-%(id)s.%(ext)s')
-               or (opts.usetitle and '%(title)s-%(id)s.%(ext)s')
-               or (opts.useid and '%(id)s.%(ext)s')
-               or (opts.autonumber and '%(autonumber)s-%(id)s.%(ext)s')
-               or DEFAULT_OUTTMPL)
-    if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
+    outtmpl = opts.outtmpl
+    if not outtmpl:
+        outtmpl = {'default': (
+            '%(title)s-%(id)s-%(format)s.%(ext)s' if opts.format == '-1' and opts.usetitle
+            else '%(id)s-%(format)s.%(ext)s' if opts.format == '-1'
+            else '%(autonumber)s-%(title)s-%(id)s.%(ext)s' if opts.usetitle and opts.autonumber
+            else '%(title)s-%(id)s.%(ext)s' if opts.usetitle
+            else '%(id)s.%(ext)s' if opts.useid
+            else '%(autonumber)s-%(id)s.%(ext)s' if opts.autonumber
+            else None)}
+    outtmpl_default = outtmpl.get('default')
+    if outtmpl_default is not None and not os.path.splitext(outtmpl_default)[1] and opts.extractaudio:
         parser.error('Cannot download a video and extract audio into the same'
                      ' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
-                     ' template'.format(outtmpl))
+                     ' template'.format(outtmpl_default))
 
     for f in opts.format_sort:
         if re.match(InfoExtractor.FormatSort.regex, f) is None:
@@ -413,7 +416,7 @@ def parse_retries(retries):
         'playlistreverse': opts.playlist_reverse,
         'playlistrandom': opts.playlist_random,
         'noplaylist': opts.noplaylist,
-        'logtostderr': opts.outtmpl == '-',
+        'logtostderr': outtmpl_default == '-',
         'consoletitle': opts.consoletitle,
         'nopart': opts.nopart,
         'updatetime': opts.updatetime,
index 98946666d5ed44202f546b606de4e50548fac35c..06fbaa3cdd830cd2e12febb45882d2cc54dc6aa9 100644 (file)
@@ -16,6 +16,7 @@
 from .utils import (
     expand_path,
     get_executable_path,
+    OUTTMPL_TYPES,
     preferredencoding,
     write_string,
 )
@@ -831,19 +832,23 @@ def _dict_from_multiple_values_options_callback(
         metavar='TYPE:PATH', dest='paths', default={}, type='str',
         action='callback', callback=_dict_from_multiple_values_options_callback,
         callback_kwargs={
-            'allowed_keys': 'home|temp|config|description|annotation|subtitle|infojson|thumbnail',
+            'allowed_keys': 'home|temp|%s' % '|'.join(OUTTMPL_TYPES.keys()),
             'process': lambda x: x.strip()},
         help=(
             'The paths where the files should be downloaded. '
-            'Specify the type of file and the path separated by a colon ":" '
-            '(supported: description|annotation|subtitle|infojson|thumbnail). '
+            'Specify the type of file and the path separated by a colon ":". '
+            'All the same types as --output are supported. '
             'Additionally, you can also provide "home" and "temp" paths. '
             'All intermediary files are first downloaded to the temp path and '
             'then the final files are moved over to the home path after download is finished. '
-            'Note that this option is ignored if --output is an absolute path'))
+            'This option is ignored if --output is an absolute path'))
     filesystem.add_option(
         '-o', '--output',
-        dest='outtmpl', metavar='TEMPLATE',
+        metavar='[TYPE:]TEMPLATE', dest='outtmpl', default={}, type='str',
+        action='callback', callback=_dict_from_multiple_values_options_callback,
+        callback_kwargs={
+            'allowed_keys': '|'.join(OUTTMPL_TYPES.keys()),
+            'default_key': 'default', 'process': lambda x: x.strip()},
         help='Output filename template, see "OUTPUT TEMPLATE" for details')
     filesystem.add_option(
         '--output-na-placeholder',
index 334e059551698768113726d11d1e041583ddda8d..da6b6797f565e4af26d139ffbd65d20ebfa2b188 100644 (file)
@@ -42,6 +42,7 @@ def __init__(self, downloader=None, already_have_thumbnail=False):
     def run(self, info):
         filename = info['filepath']
         temp_filename = prepend_extension(filename, 'temp')
+        files_to_delete = []
 
         if not info.get('thumbnails'):
             self.to_screen('There aren\'t any thumbnails to embed')
@@ -78,7 +79,7 @@ def is_webp(path):
             escaped_thumbnail_jpg_filename = replace_extension(escaped_thumbnail_filename, 'jpg')
             self.to_screen('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))
+            files_to_delete.append(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))
@@ -183,5 +184,9 @@ def is_webp(path):
         if success and temp_filename != filename:
             os.remove(encodeFilename(filename))
             os.rename(encodeFilename(temp_filename), encodeFilename(filename))
-        files_to_delete = [] if self._already_have_thumbnail else [thumbnail_filename]
+        if self._already_have_thumbnail:
+            info['__files_to_move'][thumbnail_filename] = replace_extension(
+                info['__thumbnail_filename'], os.path.splitext(thumbnail_filename)[1][1:])
+        else:
+            files_to_delete.append(thumbnail_filename)
         return files_to_delete, info
index a364237ce4e39ae7738b518f63275aed60a6c516..948c34287280bc1588d5a2e99dd92eafbe9db4dd 100644 (file)
@@ -578,7 +578,7 @@ def ffmpeg_escape(text):
                 in_filenames.append(metadata_filename)
                 options.extend(['-map_metadata', '1'])
 
-        if '__infojson_filepath' in info and info['ext'] in ('mkv', 'mka'):
+        if '__infojson_filename' in info and info['ext'] in ('mkv', 'mka'):
             old_stream, new_stream = self.get_stream_number(
                 filename, ('tags', 'mimetype'), 'application/json')
             if old_stream is not None:
@@ -586,7 +586,7 @@ def ffmpeg_escape(text):
                 new_stream -= 1
 
             options.extend([
-                '-attach', info['__infojson_filepath'],
+                '-attach', info['__infojson_filename'],
                 '-metadata:s:%d' % new_stream, 'mimetype=application/json'
             ])
 
index 7dcf12a3b3a18f76fbf0d8b334915d6bb93f6b87..7f34ac5c5ec8c1d5d1baf6000b85166986ca8205 100644 (file)
@@ -25,6 +25,7 @@ def run(self, info):
         dl_path, dl_name = os.path.split(encodeFilename(info['filepath']))
         finaldir = info.get('__finaldir', dl_path)
         finalpath = os.path.join(finaldir, dl_name)
+        self.files_to_move.update(info['__files_to_move'])
         self.files_to_move[info['filepath']] = finalpath
 
         for oldfile, newfile in self.files_to_move.items():
@@ -39,7 +40,7 @@ def run(self, info):
             if os.path.exists(encodeFilename(newfile)):
                 if self.get_param('overwrites', True):
                     self.report_warning('Replacing existing file "%s"' % newfile)
-                    os.path.remove(encodeFilename(newfile))
+                    os.remove(encodeFilename(newfile))
                 else:
                     self.report_warning(
                         'Cannot move file "%s" out of temporary directory since "%s" already exists. '
index be27a562243bb4c897538df63f3e23a149ca89d2..987f4bcc084536a51c7b08beaeb1b1a523e9dc8f 100644 (file)
@@ -4169,7 +4169,18 @@ def q(qid):
     return q
 
 
-DEFAULT_OUTTMPL = '%(title)s [%(id)s].%(ext)s'
+DEFAULT_OUTTMPL = {
+    'default': '%(title)s [%(id)s].%(ext)s',
+}
+OUTTMPL_TYPES = {
+    'subtitle': None,
+    'thumbnail': None,
+    'description': 'description',
+    'annotation': 'annotations.xml',
+    'infojson': 'info.json',
+    'pl_description': 'description',
+    'pl_infojson': 'info.json',
+}
 
 
 def limit_length(s, length):