#!/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.2 # B9 December 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. tilewidth=$tilesize # (or specify separately, if you prefer) tileheight=$tilesize # 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=16 # (or set the point size directly, if you prefer) # Sanity check and compatibility if [[ ${BASH_VERSINFO[0]} -gt 3 ]]; then timeout=0.25 # 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 if command -v gsed >/dev/null; then alias sed=gsed # Use GNU sed for MacOS & BSD 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 # IS TERMINAL SIXEL CAPABLE? read -s -t 1 -d "c" -p $'\e[c' >&2 # Send Device Attributes if ! [[ $REPLY =~ ";4;" ]]; then cat <<-EOF >&2 Error: Your terminal does not appear to support sixel graphics. Please use a sixel capable terminal, such as xterm -ti vt340, or ask your terminal manufacturer to add sixel support. If your terminal actually does support sixel, please file a bug report at http://github.com/hackerb9/lsix/issues EOF exit 1 fi # 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]} # Bug workaround: mlterm does not report number of colors. if [[ $TERM =~ mlterm ]]; then numcolors=1024; fi # 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 # Send control sequence to query the sixel graphics geometry to # find out how large of a sixel image can be shown. IFS=";" read -a REPLY -s -t ${timeout} -d "S" -p $'\e[?2;1;0S' >&2 if [[ ${REPLY[2]} -gt 0 ]]; then width=${REPLY[2]} else # Nope. Fall back to dtterm WindowOps to approximate sixel geometry. IFS=";" read -a REPLY -s -t ${timeout} -d "t" -p $'\e[14t' >&2 if [[ $? == 0 && ${REPLY[2]} -gt 0 ]]; then width=${REPLY[2]} fi fi # BUG WORKAROUND: XTerm cannot show images wider than 1000px. # Remove this hack once XTerm gets fixed. Last checked: XTerm(327) if [[ $TERM =~ xterm && $width -ge 1000 ]]; then width=1000; fi # Space on either side of each tile is less than 0.5% of total screen width tilexspace=$((width/201)) tileyspace=$((tilexspace/2)) # Figure out how many tiles we can fit per row. ("+ 1" is for -shadow). numtiles=$((width/(tilewidth + 2*tilexspace + 1))) } 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 readarray -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 # Resize on load: Save memory by appending this suffix to every filename. resize="[${tilewidth}x${tileheight}]" 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 " # Create and display montages one row at a time. while [ $# -gt 0 ]; do # While we still have images to process... 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 } 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 -v ORS="" ' 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(/\..?.?.?.?$/, ""); print 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.