'gifski' as a bash command using R

Posted on January 28, 2022 by Stéphane Laurent
Tags: R, misc

The gifski command-line utility is a great tool to make a GIF animation from a series of png files. At my work I’m using a laptop with Windows 10 and I don’t have admin rights. I don’t know how to install gifski on this laptop.

But gifski is also the name of a R package which wraps the gifski command-line utility, and this package can be installed without difficulty. So I used this package and the optparse package to make my own bash command gifski, which is more convenient than the package.

Here is the script:

suppressPackageStartupMessages(library("optparse"))
suppressPackageStartupMessages(library("gifski"))

option_list <- list( 
  make_option(
    "--frames", type = "character", 
    help = "png files given by a glob (e.g. pic*.png)"
  ),
  make_option(
    "--fps", type = "integer", default = 20L,
    help = "frames per second (default 20)"
  ),
  make_option(
    c("-l", "--loop"), type = "integer", default = 0L, 
    help = "number of loops, 0 for infinite (the default)",
    metavar = "number"
  ),
  make_option(
    c("-s", "--size"), type = "character", default = "512x512", 
    help = paste0(
      "size of the gif given in the form WxH where W is the width in pixels ", 
      "and H is the height in pixels (default 512x512)"
    ),
    metavar = "WxH"
  ),
  make_option(
    c("-b", "--backward"), action = "store_true", default = FALSE, 
    help = "loop forward and backward"
  ),
  make_option(
    c("-o", "--output"), type = "character", default = "animation.gif", 
    help = "output gif file (default animation.gif)", 
    metavar = "output.gif"
  )
)

opt <- parse_args(OptionParser(
  option_list = option_list, prog = "gifski"
))

# check options are correct
size_ok <- grepl("^\\d.*x\\d.*$", opt$size)
if(!size_ok)
  stop("Invalid 'size' option.")
if(opt$fps <= 0)
  stop("Invalid 'fps' option.")
if(opt$loop < 0)
  stop("Invalid 'loop' option.")
png_files <- Sys.glob(opt$frames)
if(length(png_files) == 0L)
  stop("Invalid 'frames' option.")

# if the user chooses the 'backward' option we duplicate the files 
#   in a temporary directory
if(opt$backward){
  npngs <- 2L * length(png_files)
  fmt <- paste0("pic%0", floor(log10(npngs) + 1), "d.png")
  new_png_files <- file.path(tempdir(), sprintf(fmt, 1L:npngs))
  file.copy(c(png_files, rev(png_files)), new_png_files)
  png_files <- new_png_files
}

# get width and height
wh <- as.numeric(strsplit(opt$size, "x")[[1L]])

# a function to avoid some printed messages
quiet <- function(x) {
  sink(tempfile())
  on.exit(sink())
  invisible(force(x))
}

# run gifski
quiet(gifski(
  png_files = png_files,
  gif_file = opt$output,
  width = wh[1L],
  height = wh[2L],
  delay = 1/opt$fps,
  loop = ifelse(opt$loop == 0L, TRUE, opt$loop)
))

cat("Output written to", opt$output)

Save this script where you want, say under the name gifski.R.

Now we make a bat file, say gifski.bat, which will run this script:

@echo off
echo.
C:\path\to\Rscript.exe C:\path\to\gifski.R %*

That’s all. Put this bat file in a folder available in the PATH environment variable and you can use the bash command gifski. Here is the help which is displayed by the command gifski --help:

Usage: gifski [options]


Options:
        --frames=FRAMES
                png files given by a glob (e.g. pic*.png)

        --fps=FPS
                frames per second (default 20)

        -l NUMBER, --loop=NUMBER
                number of loops, 0 for infinite (the default)

        -s WXH, --size=WXH
                size of the gif given in the form WxH where W is the width in pixels 
                and H is the height in pixels (default 512x512)

        -b, --backward
                loop forward and backward

        -o OUTPUT.GIF, --output=OUTPUT.GIF
                output gif file (default animation.gif)

        -h, --help
                Show this help message and exit

Note that there is an additional feature as compared to the original gifski tool: the --backward option, which allows to loop forward and backward.