From de6000d913fd35643cb6faf89919665ddd9ab225 Mon Sep 17 00:00:00 2001 From: pukkandan Date: Wed, 3 Feb 2021 19:06:09 +0530 Subject: [PATCH] Multiple output templates for different file types Syntax: -o common_template -o type:type_template Types supported: subtitle|thumbnail|description|annotation|infojson|pl_description|pl_infojson --- README.md | 26 ++-- youtube_dlc/YoutubeDL.py | 139 ++++++++++-------- youtube_dlc/__init__.py | 25 ++-- youtube_dlc/options.py | 15 +- youtube_dlc/postprocessor/embedthumbnail.py | 9 +- youtube_dlc/postprocessor/ffmpeg.py | 4 +- .../postprocessor/movefilesafterdownload.py | 3 +- youtube_dlc/utils.py | 13 +- 8 files changed, 136 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index 2ff137e450..60d32d6e31 100644 --- 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 diff --git a/youtube_dlc/YoutubeDL.py b/youtube_dlc/YoutubeDL.py index da5001f075..9631745def 100644 --- a/youtube_dlc/YoutubeDL.py +++ b/youtube_dlc/YoutubeDL.py @@ -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: diff --git a/youtube_dlc/__init__.py b/youtube_dlc/__init__.py index bb94389e58..646b135192 100644 --- a/youtube_dlc/__init__.py +++ b/youtube_dlc/__init__.py @@ -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, diff --git a/youtube_dlc/options.py b/youtube_dlc/options.py index 98946666d5..06fbaa3cdd 100644 --- a/youtube_dlc/options.py +++ b/youtube_dlc/options.py @@ -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', diff --git a/youtube_dlc/postprocessor/embedthumbnail.py b/youtube_dlc/postprocessor/embedthumbnail.py index 334e059551..da6b6797f5 100644 --- a/youtube_dlc/postprocessor/embedthumbnail.py +++ b/youtube_dlc/postprocessor/embedthumbnail.py @@ -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 diff --git a/youtube_dlc/postprocessor/ffmpeg.py b/youtube_dlc/postprocessor/ffmpeg.py index a364237ce4..948c342872 100644 --- a/youtube_dlc/postprocessor/ffmpeg.py +++ b/youtube_dlc/postprocessor/ffmpeg.py @@ -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' ]) diff --git a/youtube_dlc/postprocessor/movefilesafterdownload.py b/youtube_dlc/postprocessor/movefilesafterdownload.py index 7dcf12a3b3..7f34ac5c5e 100644 --- a/youtube_dlc/postprocessor/movefilesafterdownload.py +++ b/youtube_dlc/postprocessor/movefilesafterdownload.py @@ -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. ' diff --git a/youtube_dlc/utils.py b/youtube_dlc/utils.py index be27a56224..987f4bcc08 100644 --- a/youtube_dlc/utils.py +++ b/youtube_dlc/utils.py @@ -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):