Package

~/.local/bin/lsix

Rina Kawakita 2019. 12. 26. 22:16
~/.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.