|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- #!/usr/bin/env 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.7.1
- # B9 April 2020
-
- # 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)
-
- timeout=0.25 # How long to wait for terminal to respond
- # to a control sequence (in seconds).
-
- # Sanity checks and compatibility
- if [[ ${BASH_VERSINFO[0]} -eq 3 ]]; then
- if bash --version | head -1 | grep -q "version 3"; then
- cat <<-EOF >&2
- Error: The version of Bash is extremely out of date.
- (2007, the same year Steve Jobs announced the iPhone!)
-
- This is almost always due to Apple's MacOS being silly.
- Please let Apple know that their users expect current UNIX tools.
-
- In the meantime, try using "brew install bash".
- EOF
- exit 1
- else
- exec bash "$0" "$@" || echo "Exec failed" >&2
- exit 1
- fi
- 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? # Send Device Attributes
- IFS=";" read -a REPLY -s -t 1 -d "c" -p $'\e[c' >&2
- for code in "${REPLY[@]}"; do
- if [[ $code == "4" ]]; then
- hassixel=yup
- break
- fi
- done
-
- # YAFT is vt102 compatible, cannot respond to vt220 escape sequence.
- if [[ "$TERM" == yaft* ]]; then hassixel=yeah; fi
-
- if [[ -z "$hassixel" ]]; then
- cat <<-EOF >&2
- Error: Your terminal does not report having sixel graphics support.
-
- Please use a sixel capable terminal, such as xterm -ti vt340, or
- ask your terminal manufacturer to add sixel support.
-
- You may test your terminal by viewing a single image, like so:
-
- convert foo.jpg -geometry 800x480 sixel:-
-
- If your terminal actually does support sixel, please file a bug
- report at http://github.com/hackerb9/lsix/issues
- EOF
- read -s -t 1 -d "c" -p $'\e[c' >&2
- if [[ "$REPLY" ]]; then
- echo
- cat -v <<< "(Please mention device attribute codes: ${REPLY}c)"
- fi
-
- exit 1
- fi
-
- # ENABLE SIXEL SCROLLING so image will appear right after cursor.
- if [[ $TERM != "mlterm" ]]; then
- echo -ne $'\e[?80h'
- else
- # Except... mlterm (as of 3.5.0) has a bug that reverses the sense
- echo -ne $'\e[?80l'
- 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
- # YAFT is vt102 compatible, cannot respond to vt220 escape sequence.
- if [[ "$TERM" == yaft* ]]; then numcolors=256; 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" (DECSCNM screen mode).
- 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
- # YAFT is vt102 compatible, cannot respond to vt220 escape sequence.
- if [[ "$TERM" == yaft* ]]; then background=black; foreground=white; 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]=":${MAPFILE[$x]}[0]"
- fi
- done
- set -- "${MAPFILE[@]}"
- else
- # Command line args specified. Check for directories.
- lsix=$(realpath "$0")
- for arg; do
- if [ -d "$arg" ]; then
- echo Recursing on $arg
- (cd "$arg"; $lsix)
- else
- nodirs+=($arg)
- fi
- done
- set -- "${nodirs[@]}"
- 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 and : prefix.
- # 2. Quote percent backslash, and at sign.
- # 3. Replace control characters with question marks.
- # 4. If a filename is too long, remove extension (.jpg).
- # 5. Split long filenames with newlines (recursively)
- span=15 # filenames longer than span will be split
- echo -n "$1" |
- sed 's|^:||; 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
-
- # * Sort order is a bit strange.
- # * 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.
|