gifsicle 1.0.0
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
gifsicleis available withGifsicle.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 runGifsicleClient.version()reads the CLI version stringGifsicleClient.info()parsesgifsicle --infoGifsicleClient.frameCount()returns the number of framesGifsicleClient.size()returns the logical GIF sizeGifsicleClient.optimize()runsgifsiclewith resize, delete, delay, loop, color reduction, lossy, and optimization flagsGifsicleClient.merge()combines multiple GIF inputs into one outputGifsicleClient.explode()writes one file per frameGifsicleClient.extractFrame()writes one frame to a new GIFGifsicleClient.run()andGifsicleClient.start()expose raw process access
Testing locally #
The package test suite will use the real gifsicle binary when it is
available:
dart test