[BUGFIX] Do not fail with thumbnail issues

This commit is contained in:
Jesse Bannon 2023-07-27 23:18:02 -07:00
parent a9e6a80111
commit 867f8ff15b
8 changed files with 52 additions and 48 deletions

View file

@ -34,8 +34,8 @@ from ytdl_sub.entries.variables.kwargs import YTDL_SUB_MATCH_FILTER_REJECT
from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.utils.logger import Logger
from ytdl_sub.utils.thumbnail import ThumbnailTypes
from ytdl_sub.utils.thumbnail import convert_download_thumbnail
from ytdl_sub.utils.thumbnail import download_and_convert_url_thumbnail
from ytdl_sub.utils.thumbnail import try_convert_download_thumbnail
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
download_logger = Logger.get(name="downloader")
@ -82,8 +82,6 @@ class UrlDownloaderThumbnailPlugin(SourcePluginExtension):
# If latest entry, always update the thumbnail on each entry
if thumbnail_id == ThumbnailTypes.LATEST_ENTRY:
# Make sure the entry's thumbnail is converted to jpg
convert_download_thumbnail(entry, error_if_not_found=False)
# always save in dry-run even if it doesn't exist...
if self.is_dry_run or os.path.isfile(entry.get_download_thumbnail_path()):
@ -138,8 +136,14 @@ class UrlDownloaderThumbnailPlugin(SourcePluginExtension):
def modify_entry(self, entry: Entry) -> Optional[Entry]:
"""
Use the entry to download thumbnails (or move if LATEST_ENTRY)
Use the entry to download thumbnails (or move if LATEST_ENTRY).
In addition, convert the entry thumbnail to jpg
"""
# We always convert entry thumbnails to jpgs, and is performed here to be done
# as early as possible in the plugin pipeline (downstream plugins depend on it being jpg)
if not self.is_dry_run:
try_convert_download_thumbnail(entry=entry)
if entry.kwargs_get(COLLECTION_URL) in self._collection_url_mapping:
self._download_url_thumbnails(
collection_url=self._collection_url_mapping[entry.kwargs(COLLECTION_URL)],

View file

@ -86,10 +86,12 @@ class YTDLP:
If the entry fails to download
"""
num_tries = 0
entry_files_exist = False
copied_ytdl_options_overrides = copy.deepcopy(ytdl_options_overrides)
while not entry_files_exist and num_tries < cls._EXTRACT_ENTRY_NUM_RETRIES:
is_downloaded = False
entry_dict: Optional[Dict] = None
while num_tries < cls._EXTRACT_ENTRY_NUM_RETRIES:
entry_dict = cls.extract_info(
ytdl_options_overrides=copied_ytdl_options_overrides, **kwargs
)
@ -115,7 +117,7 @@ class YTDLP:
time.sleep(cls._EXTRACT_ENTRY_RETRY_WAIT_SEC)
num_tries += 1
# Remove the download archive so it can retry without thinking its already downloaded,
# Remove the download archive to retry without thinking its already downloaded,
# even though it is not
if "download_archive" in copied_ytdl_options_overrides:
del copied_ytdl_options_overrides["download_archive"]
@ -127,6 +129,10 @@ class YTDLP:
cls._EXTRACT_ENTRY_NUM_RETRIES,
)
# Still return if the media file downloaded (thumbnail could be missing)
if is_downloaded and entry_dict is not None:
return entry_dict
error_dict = {"ytdl_options": ytdl_options_overrides, "kwargs": kwargs}
raise FileNotDownloadedException(
f"yt-dlp failed to download an entry with these arguments: {error_dict}"

View file

@ -92,6 +92,15 @@ class Entry(EntryVariables, BaseEntry):
"""
return self.get_ytdlp_download_thumbnail_path() is not None
@final
def is_thumbnail_available(self) -> bool:
"""
Returns
-------
True if the thumbnail file exists and is its proper format. False otherwise.
"""
return os.path.isfile(self.get_download_thumbnail_path())
@final
def is_downloaded(self) -> bool:
"""

View file

@ -11,7 +11,6 @@ from ytdl_sub.utils.ffmpeg import FFMPEG
from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.utils.logger import Logger
from ytdl_sub.utils.thumbnail import convert_download_thumbnail
from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_EXTS
from ytdl_sub.validators.validators import BoolValidator
@ -90,10 +89,12 @@ class EmbedThumbnailPlugin(Plugin[EmbedThumbnailOptions]):
logger.warning("webm does not support embedded thumbnails, skipping")
return None
if not self.is_dry_run:
# convert the entry thumbnail so it is embedded as jpg
convert_download_thumbnail(entry=entry)
if not entry.is_thumbnail_downloaded():
logger.warning(
"Cannot embed thumbnail for '%s' because it is not available", entry.title
)
if not self.is_dry_run:
if entry.ext in AUDIO_CODEC_EXTS:
self._embed_audio_file(entry)
else:

View file

@ -11,7 +11,6 @@ from ytdl_sub.config.preset_options import OptionsDictValidator
from ytdl_sub.entries.entry import Entry
from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.utils.logger import Logger
from ytdl_sub.utils.thumbnail import convert_download_thumbnail
from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_EXTS
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator
@ -162,11 +161,7 @@ class MusicTagsPlugin(Plugin[MusicTagsOptions]):
)
setattr(audio_file, tag_name, tag_value[0])
if self.plugin_options.embed_thumbnail:
# convert the entry thumbnail so it is embedded as jpg
convert_download_thumbnail(entry=entry)
if self.plugin_options.embed_thumbnail and entry.is_thumbnail_available():
with open(entry.get_download_thumbnail_path(), "rb") as thumb:
mediafile_img = mediafile.Image(
data=thumb.read(), desc="cover", type=mediafile.ImageType.front

View file

@ -1,5 +1,4 @@
import copy
import os.path
from pathlib import Path
from typing import Any
from typing import List
@ -21,7 +20,6 @@ from ytdl_sub.utils.exceptions import ValidationException
from ytdl_sub.utils.ffmpeg import FFMPEG
from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.utils.thumbnail import convert_download_thumbnail
from ytdl_sub.validators.string_select_validator import StringSelectValidator
@ -184,11 +182,6 @@ class SplitByChaptersPlugin(SplitPlugin[SplitByChaptersOptions]):
f"Tried to split '{entry.title}' by chapters but it has no chapters"
)
# convert the entry thumbnail early so we do not have to guess the thumbnail extension
# when copying it. Do not error if it's not found, in case thumbnail_name is not set
if not self.is_dry_run:
convert_download_thumbnail(entry=entry, error_if_not_found=False)
for idx, title in enumerate(chapters.titles):
new_uid = _split_video_uid(source_uid=entry.uid, idx=idx)
@ -209,7 +202,7 @@ class SplitByChaptersPlugin(SplitPlugin[SplitByChaptersOptions]):
# Copy the original vid thumbnail to the working directory with the new uid. This so
# downstream logic thinks this split video has its own thumbnail
if os.path.isfile(entry.get_download_thumbnail_path()):
if entry.is_thumbnail_available():
FileHandler.copy(
src_file_path=entry.get_download_thumbnail_path(),
dst_file_path=Path(self.working_directory)

View file

@ -21,7 +21,6 @@ from ytdl_sub.utils.exceptions import ValidationException
from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.utils.thumbnail import convert_download_thumbnail
def _get_split_plugin(plugins: List[Plugin]) -> Optional[SplitPlugin]:
@ -69,16 +68,11 @@ class SubscriptionDownload(BaseSubscription, ABC):
entry=entry,
)
# TODO: see if entry even has a thumbnail
if self.output_options.thumbnail_name:
if self.output_options.thumbnail_name and entry.is_thumbnail_available():
output_thumbnail_name = self.overrides.apply_formatter(
formatter=self.output_options.thumbnail_name, entry=entry
)
# We always convert entry thumbnails to jpgs, and is performed here
if not dry_run:
convert_download_thumbnail(entry=entry)
# Copy the thumbnails since they could be used later for other things
self._enhanced_download_archive.save_file_to_output_directory(
file_name=entry.get_download_thumbnail_name(),

View file

@ -1,11 +1,14 @@
import logging
import os
import tempfile
from subprocess import CalledProcessError
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.file_handler import FileHandler
from ytdl_sub.utils.logger import Logger
from ytdl_sub.utils.retry import retry
@ -13,21 +16,18 @@ class ThumbnailTypes:
LATEST_ENTRY = "latest_entry"
def convert_download_thumbnail(entry: Entry, error_if_not_found: bool = True) -> None:
logger: logging.Logger = Logger.get("thumbnail")
def try_convert_download_thumbnail(entry: Entry) -> None:
"""
Converts an entry's downloaded thumbnail into jpg format
Converts an entry's downloaded thumbnail into jpg format.
Log with a warning if the thumbnail is not found or fails to convert
Parameters
----------
entry
Entry with the thumbnail
error_if_not_found
If the thumbnail is not found, error if True.
Raises
------
ValueError
Entry thumbnail file not found
"""
download_thumbnail_path = entry.get_ytdlp_download_thumbnail_path()
download_thumbnail_path_as_jpg = entry.get_download_thumbnail_path()
@ -37,15 +37,17 @@ def convert_download_thumbnail(entry: Entry, error_if_not_found: bool = True) ->
return
if not download_thumbnail_path:
if error_if_not_found:
raise ValueError("Thumbnail not found")
return
logger.warning("Thumbnail for '%s' was not downloaded", entry.title)
if not download_thumbnail_path == download_thumbnail_path_as_jpg:
FFMPEG.run(
["-y", "-bitexact", "-i", download_thumbnail_path, download_thumbnail_path_as_jpg]
)
FileHandler.delete(download_thumbnail_path)
try:
FFMPEG.run(
["-y", "-bitexact", "-i", download_thumbnail_path, download_thumbnail_path_as_jpg]
)
except CalledProcessError:
logger.warning("Failed to convert thumbnail for '%s' to jpg", entry.title)
finally:
FileHandler.delete(download_thumbnail_path)
@retry(times=3, exceptions=(Exception,))