Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions cms/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,19 @@
# If you plan to change this, you must also follow the instructions on admins_docs.md
# to change the equivalent value in ./frontend/src/static/js/components/media-viewer/VideoViewer/index.js and then re-build frontend

# Thumbnail GIF generation settings
THUMBNAIL_GIF_NUM_FRAMES = 5 # Number of frames to capture (for automatic mode)
THUMBNAIL_GIF_START = 3 # Start position in seconds (default: 3, matches original behavior)
THUMBNAIL_GIF_DURATION = None # Duration in seconds (None = use 25 in manual mode, full video in auto mode)
THUMBNAIL_GIF_FPS = None # Frames per second (None = use 1 in manual mode, auto-calculate in auto mode)
THUMBNAIL_GIF_AUTO_MODE = False # Enable automatic calculation (default: False for backward compatibility)
# When THUMBNAIL_GIF_AUTO_MODE = True:
# - If THUMBNAIL_GIF_DURATION is None, uses full video duration
# - Calculates FPS automatically to evenly distribute frames across duration
# When THUMBNAIL_GIF_AUTO_MODE = False:
# - Uses manual settings: THUMBNAIL_GIF_START, THUMBNAIL_GIF_DURATION, THUMBNAIL_GIF_FPS
# - Defaults match original hardcoded values (start=3, duration=25, fps=1) for backward compatibility

# how many images will be shown on the slideshow
SLIDESHOW_ITEMS = 30
# this calculation is redundant most probably, setting as an option
Expand Down
71 changes: 67 additions & 4 deletions files/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,23 +318,86 @@ def encode_media(

if profile.extension == "gif":
tf = create_temp_file(suffix=".gif")
# -ss 5 start from 5 second. -t 25 until 25 sec

# Get configuration settings with defaults matching original behavior
auto_mode = getattr(settings, 'THUMBNAIL_GIF_AUTO_MODE', False)
num_frames = getattr(settings, 'THUMBNAIL_GIF_NUM_FRAMES', 5)
gif_start = getattr(settings, 'THUMBNAIL_GIF_START', 3)
# Use None as default to distinguish between "not set" (use default) and "explicitly None" (use full video in auto mode)
gif_duration = getattr(settings, 'THUMBNAIL_GIF_DURATION', None)
gif_fps = getattr(settings, 'THUMBNAIL_GIF_FPS', None)

# Get video duration
video_duration = media.duration if media.duration and media.duration > 0 else None

# Determine effective duration and FPS
if auto_mode and video_duration:
# Automatic mode: calculate based on video length
# Use full video duration if None (explicitly set) or not configured
if gif_duration is None:
effective_duration = video_duration - gif_start
else:
effective_duration = min(gif_duration, video_duration - gif_start)

# Ensure duration is positive
if effective_duration <= 0:
effective_duration = max(1, video_duration - gif_start)

# Calculate FPS to evenly distribute frames
# For N frames over D seconds, we need fps = (N-1) / D
# This ensures frames at start, end, and evenly spaced in between
if num_frames > 1:
calculated_fps = (num_frames - 1) / effective_duration
else:
calculated_fps = 1.0 / effective_duration # At least 1 frame

# Use calculated FPS if not manually specified
if gif_fps is None:
gif_fps = calculated_fps

# Use calculated duration
final_duration = effective_duration

else:
# Manual mode or fallback when duration is missing
# Use original defaults (start=3, duration=25, fps=1) for backward compatibility
if gif_duration is None:
gif_duration = 25 # Original default
if gif_fps is None:
gif_fps = 1 # Original default
if gif_start is None or gif_start == 0:
gif_start = 3 # Original default for backward compatibility

# Cap duration if it exceeds video length
if video_duration and (gif_start + gif_duration) > video_duration:
final_duration = max(1, video_duration - gif_start)
logger.info(f"GIF duration capped to {final_duration}s for video of {video_duration}s")
else:
final_duration = gif_duration

# Ensure minimum values
gif_start = max(0, gif_start)
final_duration = max(0.1, final_duration)
gif_fps = max(0.01, gif_fps)

# Build ffmpeg command
command = [
settings.FFMPEG_COMMAND,
"-y",
"-ss",
"3",
str(gif_start),
"-i",
media.media_file.path,
"-hide_banner",
"-vf",
"scale=344:-1:flags=lanczos,fps=1",
f"scale=344:-1:flags=lanczos,fps={gif_fps:.4f}",
"-t",
"25",
str(final_duration),
"-f",
"gif",
tf,
]

ret = run_command(command)
if os.path.exists(tf) and get_file_type(tf) == "image":
with open(tf, "rb") as f:
Expand Down
Loading