From fba1c397b1715fa4d4361f62231ef283f13983af Mon Sep 17 00:00:00 2001 From: Elyse <26639800+elyse0@users.noreply.github.com> Date: Thu, 9 Mar 2023 17:32:19 -0600 Subject: [PATCH] [youtube] Support --download-sections for YT Livestream from start --- yt_dlp/YoutubeDL.py | 11 ++++++++--- yt_dlp/downloader/dash.py | 2 ++ yt_dlp/extractor/youtube.py | 38 ++++++++++++++++++++++++++++--------- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index f701738c9..322f05baa 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -26,7 +26,12 @@ from .cache import Cache from .compat import compat_os_name, compat_shlex_quote from .cookies import load_cookies -from .downloader import FFmpegFD, get_suitable_downloader, shorten_protocol_name +from .downloader import ( + DashSegmentsFD, + FFmpegFD, + get_suitable_downloader, + shorten_protocol_name, +) from .downloader.rtmp import rtmpdump_version from .extractor import gen_extractor_classes, get_info_extractor from .extractor.common import UnsupportedURLIE @@ -3143,8 +3148,8 @@ def existing_video_file(*filepaths): fd, success = None, True if info_dict.get('protocol') or info_dict.get('url'): fd = get_suitable_downloader(info_dict, self.params, to_stdout=temp_filename == '-') - if fd is not FFmpegFD and 'no-direct-merge' not in self.params['compat_opts'] and ( - info_dict.get('section_start') or info_dict.get('section_end')): + if not(fd is FFmpegFD or fd is DashSegmentsFD) and 'no-direct-merge' not in self.params['compat_opts'] and ( + info_dict.get('section_start') or info_dict.get('section_end')): msg = ('This format cannot be partially downloaded' if FFmpegFD.available() else 'You have requested downloading the video partially, but ffmpeg is not installed') self.report_error(f'{msg}. Aborting') diff --git a/yt_dlp/downloader/dash.py b/yt_dlp/downloader/dash.py index 4328d739c..10ed0cdf9 100644 --- a/yt_dlp/downloader/dash.py +++ b/yt_dlp/downloader/dash.py @@ -33,6 +33,8 @@ def real_download(self, filename, info_dict): 'filename': fmt.get('filepath') or filename, 'live': 'is_from_start' if fmt.get('is_from_start') else fmt.get('is_live'), 'total_frags': fragment_count, + 'section_start': info_dict.get('section_start'), + 'section_end': info_dict.get('section_end'), } if real_downloader: diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py index acd4077f4..eb61f9a5e 100644 --- a/yt_dlp/extractor/youtube.py +++ b/yt_dlp/extractor/youtube.py @@ -2757,6 +2757,8 @@ 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 lack_early_segments = download_start_time - (live_start_time or download_start_time) > MAX_DURATION if lack_early_segments: @@ -2797,8 +2799,7 @@ def _extract_sequence_from_mpd(refresh_sequence, immediate): fragment_base_url = fmt_info['fragment_base_url'] assert fragment_base_url - _last_seq = int(re.search(r'(?:/|^)sq/(\d+)', fragments[-1]['path']).group(1)) - return True, _last_seq + return True self.write_debug(f'[{video_id}] Generating fragments for format {format_id}') while is_live: @@ -2818,11 +2819,19 @@ def _extract_sequence_from_mpd(refresh_sequence, immediate): last_segment_url = None continue else: - should_continue, last_seq = _extract_sequence_from_mpd(True, no_fragment_score > 15) + should_continue = _extract_sequence_from_mpd(True, no_fragment_score > 15) no_fragment_score += 2 if not should_continue: continue + last_fragment = fragments[-1] + last_seq = int(re.search(r'(?:/|^)sq/(\d+)', last_fragment['path']).group(1)) + + known_fragment = next( + (fragment for fragment in fragments if f'sq/{known_idx}' in fragment['path']), None) + if known_fragment and known_fragment['end'] > section_end: + break + if known_idx > last_seq: last_segment_url = None continue @@ -2832,20 +2841,31 @@ def _extract_sequence_from_mpd(refresh_sequence, immediate): if begin_index < 0 and known_idx < 0: # skip from the start when it's negative value known_idx = last_seq + begin_index + if lack_early_segments: - known_idx = max(known_idx, last_seq - int(MAX_DURATION // fragments[-1]['duration'])) + known_idx = max(known_idx, last_seq - int(MAX_DURATION // last_fragment['duration'])) + try: for idx in range(known_idx, last_seq): # do not update sequence here or you'll get skipped some part of it - should_continue, _ = _extract_sequence_from_mpd(False, False) + should_continue = _extract_sequence_from_mpd(False, False) if not should_continue: known_idx = idx - 1 raise ExtractorError('breaking out of outer loop') + last_segment_url = urljoin(fragment_base_url, 'sq/%d' % idx) - yield { - 'url': last_segment_url, - 'fragment_count': last_seq, - } + frag_duration = last_fragment['duration'] + frag_start = last_fragment['start'] - (last_seq - idx) * frag_duration + frag_end = frag_start + frag_duration + + if frag_start >= section_start and frag_end <= section_end: + yield { + 'url': last_segment_url, + 'duration': frag_duration, + 'start': frag_start, + 'end': frag_end, + } + if known_idx == last_seq: no_fragment_score += 5 else: