diff options
author | pukkandan <pukkandan@users.noreply.github.com> | 2021-02-03 02:45:00 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-03 02:45:00 +0530 |
commit | e29663c644a65846125f5792be52dc27feb68297 (patch) | |
tree | 860d75177c52e66e92811dba694ca6c60bdc37f1 | |
parent | 9c3fe2ef809006e69b4fd4ed9ff63e9fe57f5e8d (diff) | |
download | hypervideo-pre-e29663c644a65846125f5792be52dc27feb68297.tar.lz hypervideo-pre-e29663c644a65846125f5792be52dc27feb68297.tar.xz hypervideo-pre-e29663c644a65846125f5792be52dc27feb68297.zip |
#45 Allow date/time formatting in output template
Closes #43
-rw-r--r-- | README.md | 11 | ||||
-rw-r--r-- | youtube_dlc/YoutubeDL.py | 62 | ||||
-rw-r--r-- | youtube_dlc/utils.py | 13 |
3 files changed, 63 insertions, 23 deletions
@@ -751,7 +751,9 @@ The `-o` option is used to indicate a template for the output file names while ` **tl;dr:** [navigate me to examples](#output-template-examples). -The basic usage 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. Allowed names along with sequence type are: +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 available fields are: - `id` (string): Video identifier - `title` (string): Video title @@ -870,14 +872,17 @@ youtube-dlc_test_video_.mp4 # A simple file name # Download YouTube playlist videos in separate directory indexed by video order in a playlist $ youtube-dlc -o '%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s' https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re +# Download YouTube playlist videos in seperate directories according to their uploaded year +$ youtube-dlc -o '%(upload_date>%Y)s/%(title)s.%(ext)s' https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re + # Download all playlists of YouTube channel/user keeping each playlist in separate directory: $ youtube-dlc -o '%(uploader)s/%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s' https://www.youtube.com/user/TheLinuxFoundation/playlists # Download Udemy course keeping each chapter in separate directory under MyVideos directory in your home -$ youtube-dlc -u user -p password -o '~/MyVideos/%(playlist)s/%(chapter_number)s - %(chapter)s/%(title)s.%(ext)s' https://www.udemy.com/java-tutorial/ +$ youtube-dlc -u user -p password -P '~/MyVideos' -o '%(playlist)s/%(chapter_number)s - %(chapter)s/%(title)s.%(ext)s' https://www.udemy.com/java-tutorial/ # Download entire series season keeping each series and each season in separate directory under C:/MyVideos -$ youtube-dlc -o "C:/MyVideos/%(series)s/%(season_number)s - %(season)s/%(episode_number)s - %(episode)s.%(ext)s" https://videomore.ru/kino_v_detalayah/5_sezon/367617 +$ youtube-dlc -P "C:/MyVideos" -o "%(series)s/%(season_number)s - %(season)s/%(episode_number)s - %(episode)s.%(ext)s" https://videomore.ru/kino_v_detalayah/5_sezon/367617 # Stream the video being downloaded to stdout $ youtube-dlc -o - BaW_jenozKc diff --git a/youtube_dlc/YoutubeDL.py b/youtube_dlc/YoutubeDL.py index 0b198f50d..da5001f07 100644 --- a/youtube_dlc/YoutubeDL.py +++ b/youtube_dlc/YoutubeDL.py @@ -61,6 +61,7 @@ from .utils import ( ExistingVideoReached, expand_path, ExtractorError, + float_or_none, format_bytes, format_field, formatSeconds, @@ -91,6 +92,7 @@ from .utils import ( sanitized_Request, std_headers, str_or_none, + strftime_or_none, subtitles_filename, to_high_limit_path, UnavailableVideoError, @@ -735,6 +737,11 @@ class YoutubeDL(object): try: template_dict = dict(info_dict) + template_dict['duration_string'] = ( # %(duration>%H-%M-%S)s is wrong if duration > 24hrs + formatSeconds(info_dict['duration'], '-') + if info_dict.get('duration', None) is not None + else None) + template_dict['epoch'] = int(time.time()) autonumber_size = self.params.get('autonumber_size') if autonumber_size is None: @@ -755,7 +762,8 @@ class YoutubeDL(object): template_dict = dict((k, v if isinstance(v, compat_numeric_types) else sanitize(k, v)) for k, v in template_dict.items() if v is not None and not isinstance(v, (list, tuple, dict))) - template_dict = collections.defaultdict(lambda: self.params.get('outtmpl_na_placeholder', 'NA'), template_dict) + na = self.params.get('outtmpl_na_placeholder', 'NA') + template_dict = collections.defaultdict(lambda: na, template_dict) outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL) @@ -773,27 +781,45 @@ class YoutubeDL(object): r'%%(\1)0%dd' % field_size_compat_map[mobj.group('field')], outtmpl) + # As of [1] format syntax is: + # %[mapping_key][conversion_flags][minimum_width][.precision][length_modifier]type + # 1. https://docs.python.org/2/library/stdtypes.html#string-formatting + FORMAT_RE = r'''(?x) + (?<!%) + % + \({0}\) # mapping key + (?:[#0\-+ ]+)? # conversion flags (optional) + (?:\d+)? # minimum field width (optional) + (?:\.\d+)? # precision (optional) + [hlL]? # length modifier (optional) + (?P<type>[diouxXeEfFgGcrs%]) # conversion type + ''' + + numeric_fields = list(self._NUMERIC_FIELDS) + + # Format date + FORMAT_DATE_RE = FORMAT_RE.format(r'(?P<key>(?P<field>\w+)>(?P<format>.+?))') + for mobj in re.finditer(FORMAT_DATE_RE, outtmpl): + conv_type, field, frmt, key = mobj.group('type', 'field', 'format', 'key') + if key in template_dict: + continue + value = strftime_or_none(template_dict.get(field), frmt, na) + if conv_type in 'crs': # string + value = sanitize(field, value) + else: # number + numeric_fields.append(key) + value = float_or_none(value, default=None) + if value is not None: + template_dict[key] = value + # Missing numeric fields used together with integer presentation types # in format specification will break the argument substitution since # string NA placeholder is returned for missing fields. We will patch # output template for missing fields to meet string presentation type. - for numeric_field in self._NUMERIC_FIELDS: + for numeric_field in numeric_fields: if numeric_field not in template_dict: - # As of [1] format syntax is: - # %[mapping_key][conversion_flags][minimum_width][.precision][length_modifier]type - # 1. https://docs.python.org/2/library/stdtypes.html#string-formatting - FORMAT_RE = r'''(?x) - (?<!%) - % - \({0}\) # mapping key - (?:[#0\-+ ]+)? # conversion flags (optional) - (?:\d+)? # minimum field width (optional) - (?:\.\d+)? # precision (optional) - [hlL]? # length modifier (optional) - [diouxXeEfFgGcrs%] # conversion type - ''' outtmpl = re.sub( - FORMAT_RE.format(numeric_field), + FORMAT_RE.format(re.escape(numeric_field)), r'%({0})s'.format(numeric_field), outtmpl) # expand_path translates '%%' into '%' and '$$' into '$' @@ -996,10 +1022,6 @@ class YoutubeDL(object): self.add_extra_info(ie_result, { 'extractor': ie.IE_NAME, 'webpage_url': url, - 'duration_string': ( - formatSeconds(ie_result['duration'], '-') - if ie_result.get('duration', None) is not None - else None), 'webpage_url_basename': url_basename(url), 'extractor_key': ie.ie_key(), }) diff --git a/youtube_dlc/utils.py b/youtube_dlc/utils.py index 4aaee0b5f..be27a5622 100644 --- a/youtube_dlc/utils.py +++ b/youtube_dlc/utils.py @@ -50,6 +50,7 @@ from .compat import ( compat_html_entities_html5, compat_http_client, compat_integer_types, + compat_numeric_types, compat_kwargs, compat_os_name, compat_parse_qs, @@ -3673,6 +3674,18 @@ def url_or_none(url): return url if re.match(r'^(?:(?:https?|rt(?:m(?:pt?[es]?|fp)|sp[su]?)|mms|ftps?):)?//', url) else None +def strftime_or_none(timestamp, date_format, default=None): + datetime_object = None + try: + if isinstance(timestamp, compat_numeric_types): # unix timestamp + datetime_object = datetime.datetime.utcfromtimestamp(timestamp) + elif isinstance(timestamp, compat_str): # assume YYYYMMDD + datetime_object = datetime.datetime.strptime(timestamp, '%Y%m%d') + return datetime_object.strftime(date_format) + except (ValueError, TypeError, AttributeError): + return default + + def parse_duration(s): if not isinstance(s, compat_basestring): return None |