diff --git a/modules/videos/downloader.py b/modules/videos/downloader.py index a5267f9a..5a3141b2 100755 --- a/modules/videos/downloader.py +++ b/modules/videos/downloader.py @@ -639,14 +639,18 @@ class VideoDownloader(Downloader, ABC): if not video_path.is_file(): error = f'{stdout}\n\n\n{stderr}\n\n' \ f'Video file could not be found! {video_path}' + retry_seconds = None if '!is_live' in stdout: error = f'{stdout}\n\nVideo was live and did not finish downloading.' + retry_seconds = 3600 if ' live event ' in stdout: error = f'{stdout}\n\nVideo will be live and did not finish downloading.' + retry_seconds = 3600 return DownloadResult( success=False, error=error, location=location, + retry_seconds=retry_seconds, ) if not ffmpeg_video_complete(video_path): diff --git a/modules/videos/test/test_downloader.py b/modules/videos/test/test_downloader.py index 7a44209f..8004196c 100644 --- a/modules/videos/test/test_downloader.py +++ b/modules/videos/test/test_downloader.py @@ -821,3 +821,35 @@ async def test_video_download_uses_effective_settings(test_session, test_directo assert '--sleep-requests' in cmd, 'sleep_requests override should appear in command' sleep_idx = cmd.index('--sleep-requests') assert cmd[sleep_idx + 1] == '5.0', f'Expected sleep_requests=5.0, got {cmd[sleep_idx + 1]}' + + +@pytest.mark.asyncio +async def test_live_video_retry_seconds(test_session, test_directory, mock_video_extract_info, + video_download_manager, mock_video_process_runner, + simple_channel): + """A live video download should return retry_seconds=3600 so it retries in 1 hour.""" + from wrolpi.cmd import CommandResult + + simple_channel.source_id = example_video_json['channel_id'] + simple_channel.directory = test_directory / 'videos/channel name' + simple_channel.directory.mkdir(parents=True) + + # Simulate yt-dlp output when a video is live. + mock_video_process_runner.return_value = CommandResult( + return_code=0, cancelled=False, + stdout=b'does not pass filter (!is_live), skipping ..', stderr=b'', elapsed=0, + ) + + url = 'https://www.youtube.com/watch?v=live_video' + mock_video_extract_info.return_value = example_video_json + + with mock.patch('modules.videos.downloader.VideoDownloader.prepare_filename') as mock_prepare_filename: + # Return a path that won't exist (video wasn't downloaded because it's live). + mock_prepare_filename.return_value = (simple_channel.directory / 'live video.mp4', {'id': 'foo'}) + + download = Download(url=url, downloader='video', attempts=0) + result = await video_downloader.do_download(download) + + assert result.success is False + assert result.retry_seconds == 3600 + assert 'live' in result.error.lower() diff --git a/wrolpi/downloader.py b/wrolpi/downloader.py index 22405a54..fa2538bb 100644 --- a/wrolpi/downloader.py +++ b/wrolpi/downloader.py @@ -87,6 +87,7 @@ class DownloadResult: error: str = None info_json: dict = field(default_factory=dict) location: str = None + retry_seconds: int = None success: bool = False settings: dict = field(default_factory=dict) @@ -1286,7 +1287,10 @@ async def signal_download_download(download_id: int, download_url: str): download.location = result.location or download.location or None # Clear any old errors if the download succeeded. download.error = result.error if result.error else None - download.next_download = download_manager.calculate_next_download(session, download) + if isinstance(result.retry_seconds, int): + download.next_download = now() + timedelta(seconds=result.retry_seconds) + else: + download.next_download = download_manager.calculate_next_download(session, download) urls = download.filter_excluded(result.downloads) if result.downloads else None if urls: