Skip to content
Merged
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
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 pidoshva

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: install uninstall

install:
@./install.sh

uninstall:
@./install.sh --uninstall
96 changes: 93 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,110 @@

# Installation

Run the following command to install **gifify**:
Convert videos to optimized GIFs using ffmpeg's two-pass palette technique.

gifify produces high-quality GIFs with accurate colors by generating an optimal color palette from the source video, then using it to create the final GIF.

## Features

- Two-pass palette generation for accurate colors
- Multiple resolution presets (1080p, 720p, 480p, 360p)
- Configurable frame rate
- Custom output path support
- Cross-platform ffmpeg detection and installation
- Safe temp files with automatic cleanup
- Works as a shell function (sourced) or standalone script

## Install

**One-liner (curl):**

```bash
bash -c "$(curl -fsSL https://raw.githubusercontent.com/pidoshva/gifify/main/install.sh)"
```

**From a local clone:**

```bash
git clone https://github.com/pidoshva/gifify.git
cd gifify
./install.sh
```

**Or with make:**

```bash
make install
```

The installer copies `gifify.sh` to `~/.gifify/` and adds a `source` line to your `.bashrc` or `.zshrc` (auto-detected). If `ffmpeg` is not installed, it will be installed automatically using your system's package manager.

After installing, open a new terminal or run:

```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/pidoshva/gifify/main/install.sh)"
source ~/.bashrc # or source ~/.zshrc
```

## Usage

```bash
gifify video.mp4 # Default: 1080p, 15fps
gifify video.mp4 --720p # 720p resolution
gifify video.mp4 --480p --fps 10 # 480p at 10fps
gifify video.mp4 -o output.gif # Custom output path
gifify video.mp4 --360p -o small.gif # 360p with custom output
```

## Options

| Flag | Description | Default |
|------|-------------|---------|
| `-o, --output <path>` | Output file path | `~/Desktop/gifified/<name>.gif` |
| `--fps <n>` | Frames per second | `15` |
| `--1080p` | Scale to 1920px width | (default) |
| `--720p` | Scale to 1280px width | |
| `--480p` | Scale to 854px width | |
| `--360p` | Scale to 640px width | |
| `-h, --help` | Show help | |
| `-v, --version` | Show version | |

## How It Works

1. **Palette generation** -- ffmpeg analyzes the video and generates an optimal 256-color palette using the Lanczos scaling algorithm
2. **GIF creation** -- ffmpeg re-encodes the video using the generated palette for maximum color accuracy

This two-pass approach produces significantly better results than a single-pass conversion.

## Requirements

- **ffmpeg** -- the installer handles this automatically, or install it manually:
- macOS: `brew install ffmpeg`
- Ubuntu/Debian: `sudo apt-get install ffmpeg`
- Fedora: `sudo dnf install ffmpeg`
- Arch: `sudo pacman -S ffmpeg`

## Output

By default, GIFs are saved to `~/Desktop/gifified/`. Use `-o` to specify a custom path.

## Uninstall

```bash
./install.sh --uninstall
# or
make uninstall
```

This removes `~/.gifify/` and cleans the source line from your shell RC files.

## License

The script verifies that `ffmpeg` is installed (using Homebrew or `apt-get` when possible), downloads the `gifify.sh` script, and sources it from your `~/.zshrc`. After running the installer open a new shell or run `source ~/.zshrc` to use the `gifify` command.

# Development

This repository uses GitHub Actions to run `shellcheck` on every commit.

=======

The script checks for `ffmpeg` and installs it if missing (using Homebrew or `apt-get`). It downloads the latest `gifify.sh` and adds it to your `~/.zshrc` so you can start using the function in new terminals. The installer stores a version file in `~/.gifify` and will re-download the script when a newer version is available. After installation run `source ~/.zshrc` or open a new shell and invoke `gifify`.

2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.0
1.1.0
150 changes: 131 additions & 19 deletions gifify.sh
Original file line number Diff line number Diff line change
@@ -1,16 +1,52 @@
#!/usr/bin/env bash
# gifify function

gifify() {
if ! command -v ffmpeg &> /dev/null; then
echo "Error: ffmpeg is not installed! Installing ffmpeg..."
brew install ffmpeg
fi
GIFIFY_VERSION="1.1.0"

_gifify_ensure_ffmpeg() {
command -v ffmpeg >/dev/null 2>&1 && return 0

echo "Error: ffmpeg is not installed." >&2
echo "" >&2

local pkg_cmd=""
if command -v brew >/dev/null 2>&1; then
pkg_cmd="brew install ffmpeg"
elif command -v apt-get >/dev/null 2>&1; then
pkg_cmd="sudo apt-get update && sudo apt-get install -y ffmpeg"
elif command -v dnf >/dev/null 2>&1; then
pkg_cmd="sudo dnf install -y ffmpeg"
elif command -v pacman >/dev/null 2>&1; then
pkg_cmd="sudo pacman -S --noconfirm ffmpeg"
fi

if [ -n "$pkg_cmd" ]; then
printf "Install ffmpeg now with: %s ? [y/N] " "$pkg_cmd" >&2
read -r answer
if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then
eval "$pkg_cmd" || { echo "ffmpeg installation failed." >&2; return 1; }
return 0
fi
fi

if [[ -z "$1" ]]; then
echo "Usage: gifify <video_file> [--1080p|--720p|--480p]"
echo "Please install ffmpeg manually and try again." >&2
return 1
fi
}

gifify() {
# ── Help & version ──────────────────────────────────────────────
local _gifify_usage
_gifify_usage="Usage: gifify <video_file> [options]

Options:
-o, --output <path> Output file path (default: ~/Desktop/gifified/<name>.gif)
--fps <n> Frames per second (default: 15)
--1080p Scale to 1920px width (default)
--720p Scale to 1280px width
--480p Scale to 854px width
--360p Scale to 640px width
-h, --help Show this help message
-v, --version Show version"

local input="$1"
local filename
Expand All @@ -21,19 +57,95 @@ gifify() {
local output="${output_dir}/${name}.gif"
local width="1920" # Default: 1080p width

if [[ "$*" == *"--720p"* ]]; then
width="1280"
elif [[ "$*" == *"--480p"* ]]; then
width="854"
fi
# ── Validate input ──────────────────────────────────────────────
if [ -z "$input" ]; then
echo "$_gifify_usage" >&2
return 1
fi

echo "Generating GIF from $input with width $width..."
if [ ! -e "$input" ]; then
echo "Error: file not found: $input" >&2
return 1
fi

# Generate a color palette for accurate colors
ffmpeg -y -i "$input" -vf "fps=15,scale=${width}:-1:flags=lanczos,palettegen" /tmp/palette.png
if [ ! -f "$input" ]; then
echo "Error: not a regular file: $input" >&2
return 1
fi

# Create the GIF using the generated palette
ffmpeg -y -i "$input" -i /tmp/palette.png -filter_complex "fps=15,scale=${width}:-1:flags=lanczos[x];[x][1:v]paletteuse" "$output"
# ── Check ffmpeg ────────────────────────────────────────────────
_gifify_ensure_ffmpeg || return 1

echo "GIF created at: $output"
# ── Resolve output path ─────────────────────────────────────────
if [ -z "$output" ]; then
local filename name output_dir
filename="$(basename "$input")"
name="${filename%.*}"
output_dir="$HOME/Desktop/gifified"
mkdir -p "$output_dir"
output="${output_dir}/${name}.gif"
fi

# ── Safe temp file with trap ────────────────────────────────────
local palette
palette="$(mktemp "${TMPDIR:-/tmp}/gifify_palette_XXXXXX.png")"

# Save any existing EXIT trap, set ours, restore after
local _old_trap
_old_trap="$(trap -p EXIT)"
trap 'rm -f "$palette"' EXIT

# ── Pass 1: generate palette ────────────────────────────────────
echo "[1/2] Generating palette..."
if ! ffmpeg -y -i "$input" \
-vf "fps=${fps},scale=${width}:-1:flags=lanczos,palettegen" \
"$palette" \
-loglevel error -stats; then
echo "Error: palette generation failed." >&2
rm -f "$palette"
eval "$_old_trap"
return 1
fi

# ── Pass 2: create GIF ──────────────────────────────────────────
echo "[2/2] Creating GIF..."
if ! ffmpeg -y -i "$input" -i "$palette" \
-filter_complex "fps=${fps},scale=${width}:-1:flags=lanczos[x];[x][1:v]paletteuse" \
"$output" \
-loglevel error -stats; then
echo "Error: GIF creation failed." >&2
rm -f "$palette"
eval "$_old_trap"
return 1
fi

# ── Cleanup & report ────────────────────────────────────────────
rm -f "$palette"
eval "$_old_trap"

local size
if stat --version >/dev/null 2>&1; then
# GNU stat
size="$(stat -c%s "$output" 2>/dev/null)"
else
# BSD stat (macOS)
size="$(stat -f%z "$output" 2>/dev/null)"
fi

if [ -n "$size" ]; then
local human_size
if [ "$size" -ge 1048576 ]; then
human_size="$(awk "BEGIN {printf \"%.1f MB\", $size/1048576}")"
else
human_size="$(awk "BEGIN {printf \"%.0f KB\", $size/1024}")"
fi
echo "Done! $output ($human_size)"
else
echo "Done! $output"
fi
}

# Allow running as a script: ./gifify.sh [args]
if [ "${BASH_SOURCE[0]}" = "$0" ] 2>/dev/null; then
gifify "$@"
fi
Loading
Loading