Skip to content

Instantly share code, notes, and snippets.

@judge2020
Last active March 29, 2025 13:21
Show Gist options
  • Save judge2020/fdf9387c30647da989dc68744b9e7863 to your computer and use it in GitHub Desktop.
Save judge2020/fdf9387c30647da989dc68744b9e7863 to your computer and use it in GitHub Desktop.
Trim transparency from bounds of a gif

Trim transparency from bounds of a gif

Sometimes you have a gif that has a transparent pixel border. This is inconvenient when you don't want this for chat-app emojis or stickers.

Courtest of chatgpt here's a script that takes in a gif via python input and outputs the trimmed gif.

prerequisites

  • ffmpeg
  • gifski (no video mode needed)
  • python3
  • numpy
#!/usr/bin/env python3
import os
import sys
import glob
import subprocess
import numpy as np
from PIL import Image
import time
import re
def get_nontransparent_bbox(im):
"""
Returns the bounding box (left, top, right, bottom) of the non-transparent region.
If the image is completely transparent, returns the full image box.
"""
im = im.convert("RGBA")
arr = np.array(im)
# Mask: True where alpha channel is non-zero (non-transparent)
mask = arr[..., 3] != 0
coords = np.argwhere(mask)
if coords.size == 0:
return (0, 0, im.width, im.height)
y0, x0 = coords.min(axis=0)
y1, x1 = coords.max(axis=0)
# PIL crop: right and bottom are non-inclusive, so add 1
return (x0, y0, x1 + 1, y1 + 1)
def safe_directory_name(name):
"""
Creates a directory-safe string by replacing non-alphanumeric characters with underscores.
"""
return re.sub(r'[^A-Za-z0-9_\-]', '_', name)
def main():
# Prompt user for the input GIF file
gif_file = input("Enter the name of the GIF file (in current directory): ").strip()
if not os.path.isfile(gif_file):
print(f"Error: File '{gif_file}' does not exist.")
sys.exit(1)
# Create a unique directory name for extracted frames
base_name = os.path.splitext(os.path.basename(gif_file))[0]
safe_name = safe_directory_name(base_name)
frames_dir = safe_name + "_frames"
if os.path.exists(frames_dir):
# Append a timestamp to ensure uniqueness
frames_dir += "_" + str(int(time.time()))
os.makedirs(frames_dir, exist_ok=True)
print(f"Extracting frames to directory: {frames_dir}")
# Extract frames using ffmpeg
ffmpeg_cmd = f'ffmpeg -i "{gif_file}" "{frames_dir}/%04d.png"'
print(f"Running: {ffmpeg_cmd}")
subprocess.run(ffmpeg_cmd, shell=True, check=True)
# List extracted PNG files (sorted)
png_files = sorted(glob.glob(os.path.join(frames_dir, "*.png")))
if not png_files:
print("Error: No PNG frames found after extraction.")
sys.exit(1)
# Compute the union bounding box of non-transparent areas from all frames
global_left, global_top = float('inf'), float('inf')
global_right, global_bottom = 0, 0
for png in png_files:
im = Image.open(png)
left, top, right, bottom = get_nontransparent_bbox(im)
global_left = min(global_left, left)
global_top = min(global_top, top)
global_right = max(global_right, right)
global_bottom = max(global_bottom, bottom)
print(f"Global bounding box: left={global_left}, top={global_top}, right={global_right}, bottom={global_bottom}")
# Create a directory for cropped images
cropped_dir = "cropped"
if os.path.exists(cropped_dir):
cropped_dir = safe_name + "_cropped"
if os.path.exists(cropped_dir):
cropped_dir += "_" + str(int(time.time()))
os.makedirs(cropped_dir, exist_ok=True)
# Crop each PNG to the union bounding box and save into the cropped directory
for png in png_files:
im = Image.open(png)
cropped = im.crop((global_left, global_top, global_right, global_bottom))
filename = os.path.basename(png)
cropped.save(os.path.join(cropped_dir, filename))
print(f"Cropped and saved {filename}")
# Extract the frame rate from the original GIF using ffprobe
ffprobe_cmd = (
f'ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate '
f'-of default=noprint_wrappers=1:nokey=1 "{gif_file}"'
)
fps_output = subprocess.check_output(ffprobe_cmd, shell=True).decode().strip()
num, denom = fps_output.split('/')
fps = float(num) / float(denom)
print(f"Extracted frame rate: {fps} fps")
# Use gifski to create the output GIF from the cropped images
output_gif = safe_name + "_output.gif"
gifski_cmd = f'gifski --fps {fps} -o "{output_gif}" {cropped_dir}/*.png'
print(f"Running: {gifski_cmd}")
subprocess.run(gifski_cmd, shell=True, check=True)
print(f"Created output GIF: {output_gif}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment