From fc2ba496fd09ca68c7e6eeb2c11e7000d08ff099 Mon Sep 17 00:00:00 2001
From: Lesmiscore <nao20010128@gmail.com>
Date: Sat, 17 Sep 2022 01:04:23 +0900
Subject: [PATCH] Allow open ranges for time ranges (#4940)

Authored by: Lesmiscore
---
 yt_dlp/YoutubeDL.py |  5 +++--
 yt_dlp/__init__.py  | 11 ++++++-----
 yt_dlp/options.py   |  2 +-
 3 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py
index 3b6281066..0bfc47767 100644
--- a/yt_dlp/YoutubeDL.py
+++ b/yt_dlp/YoutubeDL.py
@@ -2711,17 +2711,18 @@ class YoutubeDL:
                           (f['format_id'] for f in formats_to_download))
                 if requested_ranges:
                     to_screen(f'Downloading {len(requested_ranges)} time ranges:',
-                              (f'{int(c["start_time"])}-{int(c["end_time"])}' for c in requested_ranges))
+                              (f'{c["start_time"]:.1f}-{c["end_time"]:.1f}' for c in requested_ranges))
             max_downloads_reached = False
 
             for fmt, chapter in itertools.product(formats_to_download, requested_ranges or [{}]):
                 new_info = self._copy_infodict(info_dict)
                 new_info.update(fmt)
                 offset, duration = info_dict.get('section_start') or 0, info_dict.get('duration') or float('inf')
+                end_time = offset + min(chapter.get('end_time', duration), duration)
                 if chapter or offset:
                     new_info.update({
                         'section_start': offset + chapter.get('start_time', 0),
-                        'section_end': offset + min(chapter.get('end_time', duration), duration),
+                        'section_end': end_time if end_time < offset + duration else None,
                         'section_title': chapter.get('title'),
                         'section_number': chapter.get('index'),
                     })
diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py
index 356155fcd..87d431c6e 100644
--- a/yt_dlp/__init__.py
+++ b/yt_dlp/__init__.py
@@ -326,14 +326,15 @@ def validate_options(opts):
 
     def parse_chapters(name, value):
         chapters, ranges = [], []
+        parse_timestamp = lambda x: float('inf') if x in ('inf', 'infinite') else parse_duration(x)
         for regex in value or []:
             if regex.startswith('*'):
-                for range in regex[1:].split(','):
-                    dur = tuple(map(parse_duration, range.strip().split('-')))
-                    if len(dur) == 2 and all(t is not None for t in dur):
-                        ranges.append(dur)
-                    else:
+                for range_ in map(str.strip, regex[1:].split(',')):
+                    mobj = range_ != '-' and re.fullmatch(r'([^-]+)?\s*-\s*([^-]+)?', range_)
+                    dur = mobj and (parse_timestamp(mobj.group(1) or '0'), parse_timestamp(mobj.group(2) or 'inf'))
+                    if None in (dur or [None]):
                         raise ValueError(f'invalid {name} time range "{regex}". Must be of the form *start-end')
+                    ranges.append(dur)
                 continue
             try:
                 chapters.append(re.compile(regex))
diff --git a/yt_dlp/options.py b/yt_dlp/options.py
index 26392f619..9ad48486e 100644
--- a/yt_dlp/options.py
+++ b/yt_dlp/options.py
@@ -964,7 +964,7 @@ def create_parser():
             'Download only chapters whose title matches the given regular expression. '
             'Time ranges prefixed by a "*" can also be used in place of chapters to download the specified range. '
             'Needs ffmpeg. This option can be used multiple times to download multiple sections, '
-            'e.g. --download-sections "*10:15-15:00" --download-sections "intro"'))
+            'e.g. --download-sections "*10:15-inf" --download-sections "intro"'))
     downloader.add_option(
         '--downloader', '--external-downloader',
         dest='external_downloader', metavar='[PROTO:]NAME', default={}, type='str',