-
Notifications
You must be signed in to change notification settings - Fork 111
/
lsix
executable file
·339 lines (278 loc) · 11.3 KB
/
lsix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
#!/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.8.2
# B9 November 2023
# 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
shopt -s expand_aliases # Allow aliases for working around quirks.
if command -v gsed >/dev/null; then
alias sed=gsed # Use GNU sed for MacOS & BSD.
fi
if [[ "$COMSPEC" ]]; then
alias convert="magick convert" # Shun MS Windows' "convert" command.
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=";?c" 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" && -z "$LSIX_FORCE_SIXEL_SUPPORT" ]]; 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
# SIXEL SCROLLING (~DECSDM) is now presumed to be enabled.
# See https://github.com/hackerb9/lsix/issues/41 for details.
# 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]}
# 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(344)
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,webp,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|webp)$ ]]; 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++]="file://$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
# * 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
# * 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.