From e6e2eb00f1a6fc979d4cbb4da9d91d37a56a1e4f Mon Sep 17 00:00:00 2001 From: Elyse <26639800+elyse0@users.noreply.github.com> Date: Fri, 10 Mar 2023 15:20:40 -0600 Subject: [PATCH] Support negative durations --- yt_dlp/__init__.py | 17 ++--------------- yt_dlp/extractor/youtube.py | 5 +++-- yt_dlp/utils.py | 17 +++++++++++------ 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py index 81c970b726..4aa9dca8ea 100644 --- a/yt_dlp/__init__.py +++ b/yt_dlp/__init__.py @@ -321,25 +321,12 @@ def validate_outtmpl(tmpl, msg): del opts.outtmpl['default'] def parse_chapters(name, value): - def parse_timestamp(x): - # FIXME: Maybe there's a better way to remove parenthesis - x = x.replace('(', '').replace(')', '') - - # FIXME: This should be smarter, e.g. 'inf-1day'? - if x in ('inf', 'infinite'): - return float('inf') - - if re.match(r'[\d:]+', x): - return parse_duration(x) - - return datetime_from_str(x, precision='second', use_utc=False).timestamp() - 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 map(str.strip, regex[1:].split(',')): - # FIXME: This should match correctly '(now-1hour)-(now-20minutes)' - mobj = range_ != '-' and re.fullmatch(r'(.+)?\s*-\s*(.+)?', range_) + 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"') diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py index cc0b5401df..a0f93b2a81 100644 --- a/yt_dlp/extractor/youtube.py +++ b/yt_dlp/extractor/youtube.py @@ -2764,8 +2764,9 @@ def _live_dash_fragments(self, video_id, format_id, live_start_time, mpd_feed, m begin_index = 0 download_start_time = ctx.get('start') or time.time() - section_start = ctx.get('section_start') or 0 - section_end = ctx.get('section_end') or math.inf + + section_start = 0 if ctx.get('section_start') is None else download_start_time + ctx['section_start'] + section_end = math.inf if ctx.get('section_end') is None else download_start_time + ctx['section_end'] lack_early_segments = download_start_time - (live_start_time or download_start_time) > MAX_DURATION if lack_early_segments: diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py index ed75169676..07bc3f5a0a 100644 --- a/yt_dlp/utils.py +++ b/yt_dlp/utils.py @@ -2667,16 +2667,19 @@ def parse_duration(s): days, hours, mins, secs, ms = [None] * 5 m = re.match(r'''(?x) + (?P[+-])? (?P (?:(?:(?P[0-9]+):)?(?P[0-9]+):)?(?P[0-9]+):)? (?P(?(before_secs)[0-9]{1,2}|[0-9]+)) (?P[.:][0-9]+)?Z?$ ''', s) if m: - days, hours, mins, secs, ms = m.group('days', 'hours', 'mins', 'secs', 'ms') + sign, days, hours, mins, secs, ms = m.group('sign', 'days', 'hours', 'mins', 'secs', 'ms') else: m = re.match( - r'''(?ix)(?:P? + r'''(?ix)(?: + (?P[+-])? + P? (?: [0-9]+\s*y(?:ears?)?,?\s* )? @@ -2700,17 +2703,19 @@ def parse_duration(s): (?P[0-9]+)(?P\.[0-9]+)?\s*s(?:ec(?:ond)?s?)?\s* )?Z?$''', s) if m: - days, hours, mins, secs, ms = m.groups() + sign, days, hours, mins, secs, ms = m.groups() else: - m = re.match(r'(?i)(?:(?P[0-9.]+)\s*(?:hours?)|(?P[0-9.]+)\s*(?:mins?\.?|minutes?)\s*)Z?$', s) + m = re.match(r'(?i)(?P[+-])?(?:(?P[0-9.]+)\s*(?:hours?)|(?P[0-9.]+)\s*(?:mins?\.?|minutes?)\s*)Z?$', s) if m: - hours, mins = m.groups() + sign, hours, mins = m.groups() else: return None + sign = -1 if sign == '-' else 1 + if ms: ms = ms.replace(':', '.') - return sum(float(part or 0) * mult for part, mult in ( + return sign * sum(float(part or 0) * mult for part, mult in ( (days, 86400), (hours, 3600), (mins, 60), (secs, 1), (ms, 1)))