[BUGFIX] Retry channel image downloads, continue if it fails (#218)

This commit is contained in:
Jesse Bannon 2022-09-06 16:12:28 -07:00 committed by GitHub
parent 3e801c56eb
commit 6383d133dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 98 additions and 10 deletions

View file

@ -228,7 +228,7 @@ class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions
self,
thumbnail_url: str,
output_thumbnail_path: str,
):
) -> Optional[bool]:
"""
Downloads a thumbnail and stores it in the output directory
@ -238,12 +238,16 @@ class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions
Url of the thumbnail
output_thumbnail_path:
Path to store the thumbnail after downloading
Returns
-------
True if the thumbnail converted. None if it is missing or failed.
"""
if not thumbnail_url:
download_logger.warning("Could not find a thumbnail for %s", self.channel.uid)
return
return None
convert_url_thumbnail(
return convert_url_thumbnail(
thumbnail_url=thumbnail_url, output_thumbnail_path=output_thumbnail_path
)
@ -255,18 +259,22 @@ class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions
avatar_thumbnail_name = self.overrides.apply_formatter(
self.download_options.channel_avatar_path
)
self._download_thumbnail(
if self._download_thumbnail(
thumbnail_url=self.channel.avatar_thumbnail_url(),
output_thumbnail_path=str(Path(self.working_directory) / avatar_thumbnail_name),
)
self.save_file(file_name=avatar_thumbnail_name)
):
self.save_file(file_name=avatar_thumbnail_name)
else:
download_logger.warning("Failed to download channel's avatar image")
if self.download_options.channel_banner_path:
banner_thumbnail_name = self.overrides.apply_formatter(
self.download_options.channel_banner_path
)
self._download_thumbnail(
if self._download_thumbnail(
thumbnail_url=self.channel.banner_thumbnail_url(),
output_thumbnail_path=str(Path(self.working_directory) / banner_thumbnail_name),
)
self.save_file(file_name=banner_thumbnail_name)
):
self.save_file(file_name=banner_thumbnail_name)
else:
download_logger.warning("Failed to download channel's banner image")

View file

@ -0,0 +1,50 @@
from time import sleep
from typing import Any
from typing import Optional
from typing import Tuple
from typing import Type
from ytdl_sub.utils.logger import Logger
logger = Logger.get()
def retry(times: int, exceptions: Tuple[Type[Exception], ...], wait_sec: int = 5) -> Optional[Any]:
"""
Retry decorator
Parameters
----------
times
Number of attempts to retry
exceptions
Type of exceptions to retry against
wait_sec
Number of seconds to wait inbetween retries
Returns
-------
Any or None
If none, it implies all retries failed
"""
def decorator(func):
def newfn(*args, **kwargs):
attempt = 0
while attempt < times:
try:
return func(*args, **kwargs)
except exceptions:
logger.debug(
"Exception thrown when attempting to run %s, attempt %d of %d",
func.__name__,
attempt + 1,
times,
)
attempt += 1
sleep(wait_sec)
return None
return newfn
return decorator

View file

@ -1,8 +1,10 @@
import tempfile
from typing import Optional
from urllib.request import urlopen
from ytdl_sub.entries.entry import Entry
from ytdl_sub.utils.ffmpeg import FFMPEG
from ytdl_sub.utils.retry import retry
def convert_download_thumbnail(entry: Entry, error_if_not_found: bool = True) -> None:
@ -32,7 +34,8 @@ def convert_download_thumbnail(entry: Entry, error_if_not_found: bool = True) ->
FFMPEG.run(["-bitexact", "-i", download_thumbnail_path, download_thumbnail_path_as_jpg])
def convert_url_thumbnail(thumbnail_url: str, output_thumbnail_path: str):
@retry(times=5, exceptions=(Exception,))
def convert_url_thumbnail(thumbnail_url: str, output_thumbnail_path: str) -> Optional[bool]:
"""
Downloads and converts a thumbnail from a url into a jpg
@ -42,9 +45,15 @@ def convert_url_thumbnail(thumbnail_url: str, output_thumbnail_path: str):
URL of the thumbnail
output_thumbnail_path
Thumbnail file destination after its converted to jpg
Returns
-------
True to indicate it converted the thumbnail from url. None if the retry failed.
"""
with urlopen(thumbnail_url) as file:
with tempfile.NamedTemporaryFile() as thumbnail:
thumbnail.write(file.read())
FFMPEG.run(["-bitexact", "-i", thumbnail.name, output_thumbnail_path, "-bitexact"])
return True

View file

@ -1,8 +1,12 @@
from unittest.mock import patch
import pytest
from conftest import assert_debug_log
from e2e.expected_download import assert_expected_downloads
from e2e.expected_transaction_log import assert_transaction_log_matches
from ytdl_sub.subscriptions.subscription import Subscription
from ytdl_sub.utils.retry import logger as retry_logger
@pytest.fixture
@ -59,3 +63,20 @@ class TestChannelAsKodiTvShow:
dry_run=dry_run,
expected_download_summary_file_name="youtube/test_channel_full.json",
)
def test_channel_post_download(self, channel_as_tv_show_config, channel_preset_dict):
channel_preset_dict["ytdl_options"]["max_views"] = 1 # no downloads occur
full_channel_subscription = Subscription.from_dict(
config=channel_as_tv_show_config, preset_name="pz", preset_dict=channel_preset_dict
)
with assert_debug_log( # Ensure retry debug message is thrown
logger=retry_logger,
expected_message="Exception thrown when attempting to run %s, attempt %d of %d",
), patch( # Make sleeps instant
"ytdl_sub.utils.retry.sleep"
), patch( # Mock error when calling urlopen
"ytdl_sub.utils.thumbnail.urlopen"
) as mock_urlopen:
mock_urlopen.side_effect = [Exception("error")]
full_channel_subscription.download(dry_run=True)