gifsicle 1.0.0 copy "gifsicle: ^1.0.0" to clipboard
gifsicle: ^1.0.0 copied to clipboard

dart:io process wrapper for local or Docker-backed gifsicle workflows.

gifsicle #

gifsicle is a small Dart wrapper around the gifsicle CLI. It uses dart:io processes so you can call a locally installed binary or point the client at a Docker command prefix when that fits your deployment model better.

Features #

  • Check whether gifsicle is available with Gifsicle.isSupported()
  • Read GIF version, size, frame count, loop count, and frame delay metadata
  • Optimize and resize GIFs into a new output file
  • Delete frames during processing
  • Adjust animation delay and loop count
  • Merge multiple GIFs into one animation
  • Explode an animation into one file per frame
  • Extract a single frame into its own GIF file
  • Use either a local executable or a Docker-backed command prefix

Installation #

Add the package:

dart pub add gifsicle

Install the gifsicle binary that the package will invoke.

macOS #

brew install gifsicle

Debian or Ubuntu #

sudo apt-get update
sudo apt-get install -y gifsicle

Docker image #

If you install gifsicle into your application image, a minimal Debian-style snippet looks like this:

RUN apt-get update \
 && apt-get install -y --no-install-recommends gifsicle \
 && rm -rf /var/lib/apt/lists/*

You can verify the install with:

gifsicle --version

Quick start #

import 'package:gifsicle/gifsicle.dart';

Future<void> main() async {
  bool supported = await Gifsicle.isSupported();
  if (!supported) {
    throw StateError('gifsicle is not installed.');
  }

  GifsicleClient client = GifsicleClient();
  String version = await client.version();
  print(version);
}

Usage #

Read GIF metadata #

import 'package:gifsicle/gifsicle.dart';

Future<void> main() async {
  GifsicleClient client = GifsicleClient();
  GifsicleInfo info = await client.info(gifPath: 'input.gif');
  int frameCount = await client.frameCount(gifPath: 'input.gif');
  GifsicleSize size = await client.size(gifPath: 'input.gif');
  int? loopCount = info.loopCount;
  List<Duration?> frameDelays = info.frameDelays;

  print(info.rawOutput);
  print('frames: $frameCount');
  print('size: ${size.width}x${size.height}');
  print('loopCount: $loopCount');
  print('frameDelays: $frameDelays');
}

Optimize, resize, and drop frames #

import 'dart:io';

import 'package:gifsicle/gifsicle.dart';

Future<void> main() async {
  GifsicleClient client = GifsicleClient();
  ProcessResult result = await client.optimize(
    inputPath: 'input.gif',
    outputPath: 'output.gif',
    maxDimension: 512,
    deleteFrames: '5-20',
    delay: 8,
    loopCount: 0,
    colors: 128,
    careful: true,
    conserveMemory: true,
    lossy: 80,
  );

  if (result.exitCode != 0) {
    throw StateError(result.stderr.toString());
  }
}

lossy maps to gifsicle --lossy, which is not the same scale as ImageMagick's -quality. If you are migrating existing quality tuning, plan to re-tune that value experimentally.

deleteFrames accepts simple numeric specs such as '5', '5-20', or '1,4,8-12' and normalizes them into gifsicle frame selectors for you. delay is expressed in gifsicle's native 1/100 second units, so 8 means 0.08s. loopCount: 0 means loop forever.

Merge multiple GIFs into one animation #

import 'dart:io';

import 'package:gifsicle/gifsicle.dart';

Future<void> main() async {
  GifsicleClient client = GifsicleClient();
  ProcessResult result = await client.merge(
    inputPaths: <String>['frame-a.gif', 'frame-b.gif', 'frame-c.gif'],
    outputPath: 'merged.gif',
    loopCount: 0,
    colors: 128,
    careful: true,
  );

  if (result.exitCode != 0) {
    throw StateError(result.stderr.toString());
  }
}

Explode an animation into frame files #

import 'dart:io';

import 'package:gifsicle/gifsicle.dart';

Future<void> main() async {
  GifsicleClient client = GifsicleClient();
  ProcessResult result = await client.explode(
    inputPath: 'animated.gif',
    outputPathPrefix: 'frames/frame',
  );

  if (result.exitCode != 0) {
    throw StateError(result.stderr.toString());
  }
}

Extract the first frame #

import 'dart:io';

import 'package:gifsicle/gifsicle.dart';

Future<void> main() async {
  GifsicleClient client = GifsicleClient();
  ProcessResult result = await client.extractFrame(
    inputPath: 'animated.gif',
    outputPath: 'first-frame.gif',
    frame: 0,
  );

  if (result.exitCode != 0) {
    throw StateError(result.stderr.toString());
  }
}

Run through Docker instead of a local binary #

The client can wrap any executable plus a fixed argument prefix. That makes it easy to call docker run ... gifsicle while keeping the same method surface.

import 'dart:io';

import 'package:gifsicle/gifsicle.dart';

Future<void> main() async {
  String hostDirectory = Directory.current.path;
  GifsicleClient client = GifsicleClient(
    executable: 'docker',
    processArguments: <String>[
      'run',
      '--rm',
      '-v',
      '$hostDirectory:$hostDirectory',
      '-w',
      hostDirectory,
      'my-gifsicle-image',
      'gifsicle',
    ],
  );

  bool supported = await Gifsicle.isSupported(
    executable: client.executable,
    processArguments: client.processArguments,
  );

  if (!supported) {
    throw StateError('Docker-backed gifsicle is not available.');
  }

  await client.optimize(
    inputPath: '$hostDirectory/input.gif',
    outputPath: '$hostDirectory/output.gif',
    maxDimension: 320,
  );
}

When you use Docker, the paths passed to the client must be valid inside the container. In practice that means mounting the directory that contains your GIF files and using the mounted path in inputPath and outputPath.

API summary #

  • Gifsicle.isSupported() checks whether the configured command can run
  • GifsicleClient.version() reads the CLI version string
  • GifsicleClient.info() parses gifsicle --info
  • GifsicleClient.frameCount() returns the number of frames
  • GifsicleClient.size() returns the logical GIF size
  • GifsicleClient.optimize() runs gifsicle with resize, delete, delay, loop, color reduction, lossy, and optimization flags
  • GifsicleClient.merge() combines multiple GIF inputs into one output
  • GifsicleClient.explode() writes one file per frame
  • GifsicleClient.extractFrame() writes one frame to a new GIF
  • GifsicleClient.run() and GifsicleClient.start() expose raw process access

Testing locally #

The package test suite will use the real gifsicle binary when it is available:

dart test
0
likes
140
points
112
downloads

Documentation

API reference

Publisher

verified publisherarcane.art

Weekly Downloads

dart:io process wrapper for local or Docker-backed gifsicle workflows.

License

GPL-3.0 (license)

More

Packages that depend on gifsicle