~/.local/bin/lsix
#!/bin/bash
# lsix: like ls, but for images.
# Shows thumbnails of images with titles directly in terminal.
# Requirements: just ImageMagick (and a Sixel terminal, of course)
# Version 1.6
# B9 February 2018
# See end of file for USAGE.
# The following defaults may be overridden if autodetection succeeds.
numcolors=16 # Default number of colors in the palette.
background=white # Default montage background.
foreground=black # Default text color.
width=800 # Default width of screen in pixels.
# Feel free to edit these defaults to your liking.
#tilesize=120 # Width and height of each tile in the montage.
tilesize=800 # Width and height of each tile in the montage.
#tilewidth=$tilesize # (or specify separately, if you prefer)
tilewidth=200 # (or specify separately, if you prefer)
#tileheight=$tilesize
tileheight=100
# If you get questionmarks for Unicode filenames, try using a different font.
# You can list fonts available using `convert -list font`.
#fontfamily=Droid-Sans-Fallback # Great Asian font coverage
#fontfamily=Dejavu-Sans # Wide coverage, comes with GNU/Linux
#fontfamily=Mincho # Wide coverage, comes with MS Windows
# Default font size is based on width of each tile in montage.
#fontsize=$((tilewidth/10))
fontsize=10 # (or set the point size directly, if you prefer)
# Sanity check and compatibility
if [[ ${BASH_VERSINFO[0]} -gt 3 ]]; then
timeout=0.1 # How long to wait for terminal to respond.
else
timeout=1 # Bash 3's `read` could not handle decimals.
fi
if ! command -v montage >/dev/null; then
echo "Please install ImageMagick" >&2
exit 1
fi
cleanup() {
echo -n $'\e\\' # Escape sequence to stop SIXEL.
stty echo # Reset terminal to show characters.
exit 0
}
trap cleanup SIGINT SIGHUP SIGABRT EXIT
autodetect() {
# Various terminal automatic configuration routines.
# Don't show escape sequences the terminal doesn't understand.
stty -echo # Hush-a Mandara Ni Pari
# TERMINAL COLOR AUTODETECTION.
# Find out how many color registers the terminal has
IFS=";" read -a REPLY -s -t ${timeout} -d "S" -p $'\e[?1;1;0S' >&2
[[ ${REPLY[1]} == "0" ]] && numcolors=${REPLY[2]}
# Increase colors, if needed
if [[ $numcolors -lt 256 ]]; then
# Attempt to set the number of colors to 256.
# This will work for xterm, but fail on a real vt340.
IFS=";" read -a REPLY -s -t ${timeout} -d "S" -p $'\e[?1;3;256S' >&2
[[ ${REPLY[1]} == "0" ]] && numcolors=${REPLY[2]}
fi
# Query the terminal background and foreground colors.
IFS=";:/" read -a REPLY -r -s -t ${timeout} -d "\\" -p $'\e]11;?\e\\' >&2
if [[ ${REPLY[1]} =~ ^rgb ]]; then
# Return value format: $'\e]11;rgb:ffff/0000/ffff\e\\'.
# ImageMagick wants colors formatted as #ffff0000ffff.
background='#'${REPLY[2]}${REPLY[3]}${REPLY[4]%%$'\e'*}
IFS=";:/" read -a REPLY -r -s -t ${timeout} -d "\\" -p $'\e]10;?\e\\' >&2
if [[ ${REPLY[1]} =~ ^rgb ]]; then
foreground='#'${REPLY[2]}${REPLY[3]}${REPLY[4]%%$'\e'*}
# Check for "Reverse Video".
IFS=";?$" read -a REPLY -s -t ${timeout} -d "y" -p $'\e[?5$p'
if [[ ${REPLY[2]} == 1 || ${REPLY[2]} == 3 ]]; then
temp=$foreground
foreground=$background
background=$temp
fi
fi
fi
# Try dtterm WindowOps to find out the window size.
IFS=";" read -a REPLY -s -t ${timeout} -d "t" -p $'\e[14t' >&2
if [[ $? == 0 && ${REPLY[2]} -gt 0 ]]; then
width=${REPLY[2]}
fi
# BUG WORKAROUND: XTerm cannot show images wider than 1000px.
# Remove this hack once XTerm gets fixed. Last checked: XTerm(327)
if [[ $width -ge 1000 ]]; then width=1000; fi
# Space between tiles is about 0.5% of total screen width
tilexspace=$((width/200))
tileyspace=$((tilexspace/2))
# Figure out how many tiles we can fit per row.
numtiles=$((width/(tilewidth + 2*tilexspace)))
}
main() {
# Discover and setup the terminal
autodetect
if [[ $# == 0 ]]; then
# No command line args? Use a sorted list of image files in CWD.
shopt -s nullglob nocaseglob nocasematch
set - *{jpg,jpeg,png,gif,tiff,tif,p?m,x[pb]m,bmp,ico,svg,eps}
[[ $# != 0 ]] || exit
mapfile -t < <(printf "%s\n" "$@" | sort)
# Only show first frame of animated GIFs if filename not specified.
for x in ${!MAPFILE[@]}; do
if [[ ${MAPFILE[$x]} =~ gif$ ]]; then
MAPFILE[$x]+="[0]"
fi
done
set - "${MAPFILE[@]}"
fi
# Create an array of all filenames, interleaved with "-label:filename"
# E.g., '-label "foo"' 'foo.png' '-label "bar"' 'bar.jpg'
declare -a labeledfiles
i=0
for arg; do
labeledfiles[i++]="-label"
labeledfiles[i++]="$(processlabel "$arg")"
labeledfiles[i++]="$arg"
done
imoptions="-tile ${numtiles}x1" # Each montage is 1 row x $numtiles columns
imoptions+=" -geometry ${tilewidth}x${tileheight}>+${tilexspace}+${tileyspace}" # Size of each tile and spacing
imoptions+=" -background $background -fill $foreground" # Use terminal's colors
imoptions+=" -auto-orient " # Properly rotate JPEGs from cameras
if [[ $numcolors -gt 16 ]]; then
imoptions+=" -shadow " # Just for fun :-)
fi
# See top of this file to change fontfamily and fontsize.
[[ "$fontfamily" ]] && imoptions+=" -font $fontfamily "
[[ "$fontsize" ]] && imoptions+=" -pointsize $fontsize "
if [[ $# -le 21 ]]; then
# Only a few pictures to show, do it in one (multipage) chunk.
montage "${labeledfiles[@]}" $imoptions gif:- \
| convert - -colors $numcolors sixel:-
else
# Lots of pictures, so show them a row at a time instead
# of taking a long time to make one huge montage.
while [ $# -gt 0 ]; do
onerow=()
goal=$(($# - numtiles)) # How many tiles left after this row
while [ $# -gt 0 -a $# -gt $goal ]; do
len=${#onerow[@]}
onerow[len++]="-label"
onerow[len++]=$(processlabel "$1")
onerow[len++]="$1"
shift
done
montage "${onerow[@]}" $imoptions gif:- \
| convert - -colors $numcolors sixel:-
done
fi
}
processlabel() {
# This routine is all about appeasing ImageMagick.
# 1. Remove silly [0] suffix. Quote percent backslash, and at sign.
# 2. Replace control characters with question marks.
# 3. If a filename is too long, remove extension (.jpg).
# 4. Split long filenames with newlines (recursively)
span=15 # filenames longer than span will be split
echo -n "$1" |
sed 's|\[0]$||;' | tr '[:cntrl:]' '?' |
awk -v span=$span '
function halve(s, l,h) { # l and h are locals
l=length(s); h=int(l/2);
if (l <= span) { return s; }
return halve(substr(s, 1, h)) "\n" halve(substr(s, h+1));
}
{
if ( length($0) > span ) gsub(/\..?.?.?.?$/, "");
printf halve($0);
}
' |
sed 's|%|%%|g; s|\\|\\\\|g; s|@|\\@|g;'
}
####
main "$@"
# Send an escape sequence and wait for a response from the terminal
# so that the program won't quit until images have finished transferring.
read -s -t 60 -d "c" -p $'\e[c' >&2
######################################################################
# NOTES:
# Usage: lsix [ FILES ... ]
# * FILES can be any image file that ImageMagick can handle.
#
# * If no FILES are specified the most common file extensions are tried.
# (For now, lsix only searches the current working directory.)
#
# * Non-bitmap graphics often work fine (.svg, .eps, .pdf, .xcf).
#
# * Files containing multiple images (e.g., animated GIFs) will show
# all the images if the filename is specified at the command line.
# Only the first frame will be shown if "lsix" is called with no
# arguments.
#
# * Because this uses escape sequences, it works seamlessly through ssh.
#
# * If your terminal supports reporting the background and foreground
# color, lsix will use those for the montage background and text fill.
#
# * If your terminal supports changing the number of color registers
# to improve the picture quality, lsix will do so.
# * Only software needed is ImageMagick (e.g., apt-get install imagemagick).
# Your terminal must support SIXEL graphics. E.g.,
#
# xterm -ti vt340
# * To make vt340 be the default xterm type, set this in .Xresources:
#
# ! Allow sixel graphics. (Try: "convert -colors 16 foo.jpg sixel:-").
# xterm*decTerminalID : vt340
# * Xterm does not support reporting the screen size in pixels unless
# you add this to your .Xresources:
#
# ! Allow xterm to read the terminal window size (op #14)
# xterm*allowWindowOps : False
# xterm*disallowedWindowOps : 1,2,3,4,5,6,7,8,9,11,13,18,19,20,21,GetSelection,SetSelection,SetWinLines,SetXprop
# * Be cautious using lsix on videos (lsix *.avi) as ImageMagick will
# try to make a montage of every single frame and likely exhaust
# your memory and/or your patience.
# BUGS
# * Directories are not handled nicely.
# * ImageMagick's Montage doesn't handle long filenames nicely.
# * Some transparent images (many .eps files) presume a white background
# and will not show up if your terminal's background is black.
# * This file is getting awfully long for a one line kludge. :-)
# LICENSE INFORMATION
# (AKA, You know your kludge has gotten out of hand when...)
# Dual license:
# * You have all the freedoms permitted to you under the
# GNU GPL >=3. (See the included LICENSE file).
# * Additionally, this program can be used under the terms of whatever
# license 'xterm' is using (now or in the future). This is primarily
# so that, if the xterm maintainer (currently Thomas E. Dickey) so
# wishes, this program may be included with xterm as a Sixel test.
# However, anyone who wishes to take advantage of this is free to do so.