iv is a command-line image viewer using terminal graphics (Sixel, iTerm,
Kitty).
Installing | Building | Using | Sizing | Formats | Releases
iv is a command-line image viewer using terminal graphics.
Uses Sixel, iTerm Inline Images, or Kitty graphics protocols where available. See Are We Sixel Yet? for a list of terminals known to work with this package.
iv can be installed via Release, via Homebrew, via AUR, via
Scoop or via Go:
- Download a release for your platform
- Extract the
ivoriv.exefile from the.tar.bz2or.zipfile - Move the extracted executable to somewhere on your
$PATH(Linux/macOS) or%PATH%(Windows)
Install iv from the kenshaw/iv tap in the usual way with the brew
command:
# install
$ brew install kenshaw/iv/ivInstall iv from the Arch Linux AUR in the usual way with the yay
command:
# install
$ yay -S iv-cliAlternately, build and install using makepkg:
# clone package repo and make/install package
$ git clone https://aur.archlinux.org/iv-cli.git && cd iv-cli
$ makepkg -si
==> Making package: iv-cli 0.4.4-1 (Sat 11 Nov 2023 02:28:28 PM WIB)
==> Checking runtime dependencies...
==> Checking buildtime dependencies...
==> Retrieving sources...
...Install iv using Scoop:
# Optional: Needed to run a remote script the first time
> Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
# install scoop if not already installed
> irm get.scoop.sh | iex
# install iv with scoop
> scoop install ivInstall iv in the usual Go fashion:
# install latest iv version
$ go install github.com/kenshaw/iv@latestNote that this builds from source, and so needs what Building does.
iv is cgo code. It links libvips for the image formats and pdfs the Go
decoders do not cover, and fontconfig and freetype for the document renderer,
so a Go toolchain on its own is not enough. Go 1.27 or later, plus the
following per platform.
$ sudo apt-get install -y build-essential pkg-config libvips-dev \
libfontconfig-dev libfreetype-devlibvips must be 8.18 or later -- the bindings iv uses are generated against
it, and Ubuntu 24.04 and earlier ship 8.15.
Debian and Ubuntu build libheif without any codec, so HEIC and AVIF need the plugins on top of that:
$ sudo apt-get install -y libheif-plugin-libde265 libheif-plugin-x265 \
libheif-plugin-aomdec libheif-plugin-aomenc$ brew install vips pkgconfSome libvips builds put -Xpreprocessor in their pkg-config cflags, which cgo
refuses to pass through. If the build stops on that, allow it:
$ export CGO_CFLAGS_ALLOW='-Xpreprocessor'Build inside MSYS2, in the UCRT64 environment rather than MINGW64: the
bundled mupdf calls __intrinsic_setjmpex, which only the UCRT runtime has.
$ pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-pkgconf \
mingw-w64-ucrt-x86_64-libvips# build, vet, and test
$ go build ./...
$ go vet ./...
$ go test ./...
# render everything in testdata/ through the pipeline, as a smoke test --
# uses ./iv when it is there, and the iv on $PATH otherwise
$ go build -o ./iv . && ./test.sh
# a release build: versioned, stripped, and packed
$ ./build.sh -v v0.1.0build.sh writes to build/<os>/<arch>/<version>/. -r takes the version
from the latest tag instead of -v, -i installs rather than packing, -b
builds without packing, -a cross compiles to another arch, and -s makes a
fully static linux binary.
Three decoders shell out. Without the command, the file fails to render with
<command> not in path rather than being quietly skipped:
| Command | Used for |
|---|---|
soffice |
Word, Excel, PowerPoint, and OpenDocument documents |
ffmpeg |
video snapshots, and the waveform on an audio card |
binwalk |
images embedded in otherwise unrecognized files |
An audio card is the exception: it is drawn without a waveform rather than not at all.
On 386 and arm, epub, xps, mobi, fb2 and psd do not decode:
go-fitz vendors its prebuilt mupdf for 64-bit targets only, and nothing
registers for those formats there.
$ iv /path/to/image_or_directory
# a data: URL or WIFI: code
$ iv 'WIFI:S:mynetwork;T:WPA;P:secret;;'
# a remote image
$ iv https://example.com/image.png
# a specific page of a pdf, epub, or comic archive
$ iv -p 4 /path/to/document.pdf
# play an animation -- a gif, a video, a lottie
$ iv --animate /path/to/clip.webm
# and again until control-c
$ iv --loop /path/to/animation.gif
# at a rate of your own, when the default flickers
$ iv --fps 8 /path/to/clip.webm
# convert instead of displaying -- the encoder follows the output extension
$ iv --out out.webp /path/to/image.heic
# the registered decoders, encoders, and file extensions
$ iv --list
# all command line options
$ iv --helpDrawing to a terminal, iv fits an image to it. Every measure is in pixels.
The display size (-W/-H) is a ceiling and the minimum size (-w/-h, 64
by default) a floor, and between the two an image is shown at its own size
-- so an ordinary image is never resampled, and only one too big to fit or too
small to make out is touched at all. An image above the ceiling is shrunk to
it; one below the floor is grown up to it, and no further.
Leave -W/-H at 0 and the ceiling comes from the terminal itself, which is
the usual case. iv asks the kernel for the window size and uses the pixel
geometry the terminal reports there; a terminal that reports only a character
grid has its pixel size estimated from it instead. Two rows are kept back for
the file name and the prompt that follow the image.
A file written with --out gets no ceiling from the terminal -- there is no
terminal to fit, and a conversion that quietly downscaled to whatever window
happened to be open would be a surprising thing for a conversion to do. An
explicit -W/-H still applies.
-m/--mode changes how the two are used:
| Mode | What it does |
|---|---|
best-fit |
the default, described above |
none |
no scaling at all; every image is shown at its own size |
width |
best-fit against the width alone, however tall the result runs |
height |
best-fit against the height alone |
shrink |
best-fit without the floor: a small image is left small |
stretch |
fill -W x -H exactly, disregarding the aspect ratio |
Where the floor and the ceiling disagree -- a minimum larger than the room there is to show it in -- the ceiling wins. An image that clears the floor in one dimension is not grown to clear it in the other, so a 1000x2 banner stays 1000x2 rather than becoming 32000 wide.
An image small enough to be icon art is magnified by a whole number of pixels and not resampled, so a 24x24 favicon is its own pixels drawn three times larger rather than blurred up to 72. A vector -- svg, lottie, pdf -- has no pixels of its own to preserve, so it is rasterized at the size it is displayed at instead of being resampled to it, and comes out sharp at any size.
-a/--animate plays an animated file instead of showing a still of it, and
-l/--loop plays it over and over until it is interrupted with control-c.
Either one takes exactly one file -- the frames are drawn over the top of
one another and there is one screen to draw them on -- and needs a terminal to
draw to, so neither goes with --out.
The screen is cleared, the file name is written at the top of it, and the frames follow underneath, each one clearing the one before it.
A pass runs for a minute at most, or for the animation's own length when that is shorter, and what it plays at depends on what it is playing:
| Format | What it plays |
|---|---|
| Animated GIF | every frame, at the delays the file records |
| Lottie, including dotLottie | its timeline, at the frame rate the composition declares |
Video (needs ffmpeg) |
12 frames a second, sampled across its length -- below |
A video is sampled rather than played: iv reads its length, works out how
many frames a pass has room for, and takes them at even intervals across the
whole of it -- so a two hour film comes out as the shape of the film in a
minute, not its first minute at the right speed. What plays is a sampling
either way, and there is nothing in one that twelve frames a second does not
carry as well as thirty; what the terminal gets out of the difference is a
steadier picture, every frame being a clear and an encode of a whole image.
The frames are exported while the ones before them are on screen rather than
all up front, so the first frame is drawn straight away.
--fps N overrides all of that and shows every frame for 1/N of a second. It
is the rate the terminal is redrawn at, so lowering it is the thing to reach
for when the picture flickers -- and because every frame is still shown, a rate
below an animation's own plays it slower than it was drawn for. Passing it is
enough to animate; --animate is implied.
Drawing a frame is not free: encoding an image for a terminal costs real time, and how much varies with the protocol, the terminal, and the size of the image. So the playback runs against the clock rather than against its own drawing -- each frame has a moment it belongs at, and one whose moment has passed while the frame before it was still being drawn is dropped rather than played late. An animation keeps its own speed on a terminal that cannot draw it that fast; it just shows fewer of the frames.
A file with nothing to play -- a jpeg, a single frame gif -- is not animated,
and --animate says so rather than sitting on a still image.
iv --list prints what the binary in front of you actually has. Everything
below is in a default build; the decoders marked needs are only as good as
the command they shell out to, and the libvips formats depend on how libvips
itself was compiled.
Anything holding more than one image is shown one at a time, and -p N picks
which: a pdf or epub page, an icon size, a comic archive page, a lottie frame.
An animated gif or webp is the exception, and always shows its first frame
-- see Animating for playing one instead.
| Format | Extensions | Decoder |
|---|---|---|
| Portable Network Graphics, including APNG | png |
png |
| JPEG | jpg jpeg jpe jif jfif jfi |
jpeg |
| GIF, first frame of an animation | gif |
gif |
| WebP, lossy and lossless | webp |
nativewebp |
| TIFF | tif tiff |
tiff |
| Windows Bitmap | bmp dib |
bmp |
| Netpbm, raw and plain | pbm pgm ppm pnm pam |
netpbm |
| Windows icons and cursors | ico cur |
ico |
| Apple Icon Image | icns |
icns |
| HEIC/HEIF and AVIF | heic heif avif |
vips |
| JPEG 2000 and JPEG XL | jp2 jpf j2k jxl jxs |
vips |
| OpenEXR, Radiance HDR, PFM | exr hdr pfm rad |
vips |
| FITS, MATLAB, NIfTI, native vips | fits mat nii v |
vips |
| Format | Extensions | Decoder |
|---|---|---|
| SVG, plain and gzipped | svg svgz |
resvg |
| Lottie animations, and dotLottie | json lot lottie |
lottie |
| Graphviz graph description | gv dot |
graphviz |
| Mermaid diagrams | mmd mermaid |
mermaid |
A .json is only taken as a lottie when the document itself says so, so an
ordinary json file is left alone.
Mermaid diagrams are rendered in process, with no browser and no Node -- the
renderer is WebAssembly, run on wazero. --mermaid-theme picks one of
default, dark, forest, neutral or modern. Compiling the module takes
over a second, so the compiled code is cached under the user cache directory
(iv/wazero) and every run after the first starts in tens of milliseconds.
It is a reimplementation of Mermaid rather than a port, so a diagram comes out
Mermaid-like rather than identical to what mermaid.js draws, and the
iconify icon packs the mermaid cli takes are not supported -- an fa:fa-globe
in a label renders as that text.
A .mmd the renderer cannot parse is not an error: it is plain text, so it
falls through to blitz, which renders the source as a document. Run with
-v to see what the renderer made of it.
| Format | Extensions | Decoder |
|---|---|---|
| PDF, including password protected | pdf |
vips-pdf |
| EPUB, XPS, MOBI, FictionBook, Photoshop | epub xps oxps mobi fb2 psd |
fitz |
| Markdown, HTML, and plain text | md markdown mkd mdown html htm xhtml txt |
blitz |
| Comic book archives | cbz cbr cbt cb7 |
archives |
Word, Excel, PowerPoint (needs soffice) |
doc docx dot dotx xls xlsx xlt xltx ppt pptx pot potx pps ppsx |
libreoffice |
OpenDocument (needs soffice) |
odt ods odp odg odf odc ott ots otp otg |
libreoffice |
RTF, Publisher, Visio, WordPerfect, csv, tsv (needs soffice) |
rtf pub vsd wpd csv tsv |
libreoffice |
| Format | Extensions | Decoder |
|---|---|---|
Video, as a single frame (needs ffmpeg) |
mp4 m4v mkv mov avi webm mpeg mpeg2 mpg mpg2 flv asf wmv 3gp 3g2 mj2 ogv |
ffmpeg |
| Audio, as a card of its cover art and tags | mp3 m4a m4b m4p flac ogg oga dsf aac |
tag |
The audio card draws a waveform when ffmpeg is there, and is drawn without
one when it is not. -t picks the moment a video is snapshotted at, and
--animate plays it instead of snapshotting it at all.
| Format | Extensions | Decoder |
|---|---|---|
| Font specimen previews | ttf ttc otf woff woff2 sfnt eot pfb |
fontimg |
| Icons in a Windows PE | exe dll mui |
winres |
| Argument | Example | Decoder |
|---|---|---|
data: URLs |
data:image/png;base64,iVBOR... |
data |
WIFI: codes, as a QR code |
WIFI:S:mynetwork;T:WPA;P:secret;; |
qr |
http:// and https:// |
a page, or the image it names | blitz-url |
A file nothing above recognizes is handed to binwalk, which renders an image
found inside it -- the Affinity formats (afdesign, afphoto, afpub) among
them. Needs binwalk.
Without --out, iv draws to the terminal with Kitty, iTerm, or Sixel
graphics. With it, the encoder follows the output extension, and --encoder
overrides that:
| Extension | Encoder |
|---|---|
png |
png |
jpg |
jpeg |
webp |
nativewebp |
avif |
vips-avif |
gif |
vips-gif |
heic |
vips-heif |
jp2 |
vips-jp2k |
jxl |
vips-jxl |
tiff |
vips-tiff |
libvips writes several of these itself, which --encoder reaches:
vips-webp instead of nativewebp, or vips-tiff for a file the Go encoder
has no compression for.