Improves thumbnail extraction reliability

Enhances thumbnail extraction by retrying without seeking
if the initial attempt fails.

This addresses issues where the generated thumbnail file
is either missing or empty. It also calculates the seek position
more accurately and avoid seeking for very short videos.
This commit is contained in:
Jakub Jezek 2025-04-17 12:50:28 +02:00
parent c1d7cde34d
commit ca12d13a40
No known key found for this signature in database
GPG key ID: 06DBD609ADF27FD9

View file

@ -486,25 +486,37 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
# Set video input attributes
max_int = str(2147483647)
video_data = get_ffprobe_data(video_file_path, logger=self.log)
# Use duration of the individual streams since it is returned with
# higher decimal precision than 'format.duration'. We need this
# more precise value for calculating the correct amount of frames
# for higher FPS ranges or decimal ranges, e.g. 29.97 FPS
duration = max(
float(stream.get("duration", 0))
for stream in video_data["streams"]
if stream.get("codec_type") == "video"
)
# Get duration or use a safe default (single frame)
duration = 0
for stream in video_data["streams"]:
if stream.get("codec_type") == "video":
stream_duration = float(stream.get("duration", 0))
if stream_duration > duration:
duration = stream_duration
# For very short videos, just use the first frame
# Calculate seek position safely
seek_position = 0
if duration > 0.1: # Only use timestamp calculation for videos longer than 0.1 seconds
seek_position = duration * self.duration_split
# Build command args
cmd_args = [
"-y",
"-ss", str(duration * self.duration_split),
"-i", video_file_path,
"-analyzeduration", max_int,
"-probesize", max_int,
"-frames:v", "1"
]
# Only add -ss if we're seeking to a specific position
if seek_position > 0:
cmd_args.insert(1, "-ss")
cmd_args.insert(2, str(seek_position))
# Ensure we extract exactly one frame
cmd_args.extend(["-frames:v", "1"])
# add output file path
cmd_args.append(output_thumb_file_path)
@ -517,9 +529,34 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
# run subprocess
self.log.debug("Executing: {}".format(" ".join(cmd)))
run_subprocess(cmd, logger=self.log)
self.log.debug(
"Thumbnail created: {}".format(output_thumb_file_path))
return output_thumb_file_path
# Verify the output file was created
if os.path.exists(output_thumb_file_path) and os.path.getsize(output_thumb_file_path) > 0:
self.log.debug(
"Thumbnail created: {}".format(output_thumb_file_path))
return output_thumb_file_path
else:
self.log.warning(
"Output file was not created or is empty: {}".format(output_thumb_file_path))
# Fallback to extracting the first frame without seeking
if "-ss" in cmd_args:
self.log.debug("Trying fallback without seeking")
# Remove -ss and its value
ss_index = cmd_args.index("-ss")
cmd_args.pop(ss_index) # Remove -ss
cmd_args.pop(ss_index) # Remove the timestamp value
# Create new command and try again
cmd = get_ffmpeg_tool_args("ffmpeg", *cmd_args)
self.log.debug("Fallback command: {}".format(" ".join(cmd)))
run_subprocess(cmd, logger=self.log)
if os.path.exists(output_thumb_file_path) and os.path.getsize(output_thumb_file_path) > 0:
self.log.debug("Fallback thumbnail created: {}".format(output_thumb_file_path))
return output_thumb_file_path
return None
except RuntimeError as error:
self.log.warning(
"Failed intermediate thumb source using ffmpeg: {}".format(