#!/usr/bin/sh
# fvi - file metainformation lister
#
# Copyright (c) 2009-2026 Dan Fandrich <dan@coneharvesters.com>
# Licensed under the MIT license (see LICENSE).
#
# Format of file type entries in this file:
#
# * The requires: comment lines below the file type refer to the
#   software package(s) required to view the metadata of the file
#   (RedHat package names are listed in most cases).  This information is not
#   currently used by the program but is merely a note to the curious user
#   who wants to know how to look inside a new file type.
#
# * An filetype_label line must be found within the code handling each
#   file type.  Any special shell characters must be enclosed in single
#   quotes.  These lines are listed (along with the Also lines) using the
#   -v -l option.
#
# * A comment line beginning See gives a URL with more information about
#   the file format. This information is not currently used by
#   the program but is merely a note to the curious user.
#
# * A comment line beginning Also gives an alternative file name for the
#   following file type similar to the format of filetype_label, except
#   that the comment ends with the file extension and special shell characters
#   must not be quoted.  These lines are listed along with the filetype_label
#   lines using the -v -l option.
#
# * Each case entry line matching a file extension must have a tab character
#   in the first position.  These extensions are listed using the -l option.

usage () {
	echo 'fvi ver. 8'
	echo 'Displays metadata for many file types.'
	echo ''
	echo 'Usage: fvi [-v] [-?] [-h] [-l] [-t type] file1 [ file2 ... ]'
	echo '  -v     show verbose output (more than once for more verbosity)'
	echo '  -h, -? show this help'
	echo '  -l     list supported file extensions (or file types with -v)'
	echo '  -t     treat the files as having the specified file extension'
	exit 3
}

if [ "$1" = "-v" ] ; then
	FV_VERBOSE=1
	shift
	if [ "$1" = "-v" ] ; then
		FV_VERBOSE=2
		shift
	fi
fi

if [ $# -eq 0 -o "$1" = "-?" -o "$1" = "-h"  ] ; then
	usage
fi

if [ "$1" = "-l" ] ; then
	# requires: textutils, sed
	if [ -n "$FV_VERBOSE" ] ; then
		# Scan this script looking for label lines and Also comments
		sed -n -e "s/'//g" -e 's/^[^#]\+filetype[_]label //p' \
		 -e 's/^[ 	]*# Also \(.*\) [\.a-z0-9A-Z,_]*$/\1/p' "$0" | sort -f | uniq
	else
		echo 'Supported file extensions:'
		# Scan this script looking for wildcard case statement entries
		sed -n -e 's/^[ 	][ 	]*\(\*[^#)]*\)[)\\].*$/\1/p' "$0" | sed -e 's/ *| */\
/g' | sed -e 's/\*//g' -e '/^\**$/d' | sort -f | uniq | fmt
	fi
	exit
fi

staticfiletype=""
if [ "$1" = "-t" ] ; then
	staticfiletype="$2"
	# Make sure file type starts with . for comparision purposes
	case "$staticfiletype" in
		.*) ;;
		 *)	staticfiletype=".$staticfiletype"
		    ;;
	esac
	shift
	shift
fi

if [ $# -eq 0 ] ; then
	usage
fi

filetype_label () {
	# Display the file type in verbose mode
	if [ -n "$FV_VERBOSE" ] ; then
		echo "$*"
		if [ "$FV_VERBOSE" = 2 ] ; then
		  # echo the actual command used to display the file
		  set -x
		fi
	fi
}

# Make a filename starting with a dash - safe to provide a program that
# would interpret it as an option.
safefn () {
	case "$1" in
		-*) echo "./$1" ;;
		*) echo "$1" ;;
	esac
}

# Check if 'tar' needs the --wildcards option for globbing
# Set the TAR_WILDCARDS environment variable with the appropriate option
TAR_WILDCARDS=
checked_tar_wildcards=
check_tar_wildcards () {
	if [ -n "$checked_tar_wildcards" ]; then
		# We've already done the check
		return
	fi
	if tar --help 2>&1 | grep '[-]-wildcards' >/dev/null; then
		TAR_WILDCARDS=--wildcards
	fi
	checked_tar_wildcards=yes
}

for f in "$@" ; do
	# "safe" filename guaranteed not to start with a dash
	sf="$(safefn "$f")"
	if [ -z "$staticfiletype" ] ; then
		filetype="$(basename "$sf")"
	else
		filetype="$staticfiletype"
	fi
	echo "$f":
	case "$filetype" in

		# requires: unzip, xmlstarlet
		*.3mf)	filetype_label 3MF 3-D Manufacturing file
			TMPFILE="$(mktemp)"
			unzip -pq "$sf" 3D/3dmodel.model > "${TMPFILE}"
			# This could probably be combined into a single xmlstarlet invocation for speed
			printf 'Name: '
			xmlstarlet sel -t -v "/*[local-name()='model']/*[local-name()='metadata'][@name]" < "${TMPFILE}"
			printf '\n'
			xmlstarlet sel -t -v "count(/*[local-name()='model']/*[local-name()='resources']/*[local-name()='object'])" < "${TMPFILE}"
			printf ' object(s)\n'
			xmlstarlet sel -t -v "count(/*[local-name()='model']/*[local-name()='build']/*[local-name()='item'])" < "${TMPFILE}"
			printf ' build item(s)\n'
			xmlstarlet sel -t -v "count(/*[local-name()='model']/*[local-name()='resources']/*[local-name()='basematerials']/*[local-name()='base'])" < "${TMPFILE}"
			printf ' material(s)\n'
			rm -f "${TMPFILE}"
			;;

		# See https://p7zip.sourceforge.net/
		# requires: p7zip
		*.7z)	filetype_label 7zip archive
			7za l -slt -bso0 -- "$f" | sed '/^----------/,$d'
			;;

		# requires: unzip, xmlstarlet
		*.amf)	filetype_label Additive Manufacturing File
			if [ "$(dd if="$f" bs=1 count=2 2>/dev/null)" = "PK" ]; then
				# If the amf file is zip compressed, uncompress it first
				TMPFILE="$(mktemp)"
				INFILE="${TMPFILE}"
				unzip -pq "$sf" > "$INFILE"
			else
				TMPFILE=
				INFILE="$f"
			fi
			# BUG: There will be no separators between names if they come from
			# multiple objeccts. But, it's often the case that there is only a single
			# object
			xmlstarlet sel  -t \
				-o 'Name: ' -v "/amf/metadata[@type='name'] | /amf/object/metadata[@type='name']" -n  \
			    -v "count(/amf/object)" -o " object(s)" -n \
			    -v "count(/amf/material)" -o ' material(s)' -n \
				< "$INFILE"
			if [ -n "$TMPFILE" ]; then
				rm -f "$TMPFILE"
			fi
			;;

		# requires Android SDK
		*.apk)	filetype_label Android package
			aapt d --include-meta-data badging "$f"
			;;

		# See https://sourceforge.net/projects/avifile/
		# requires: avifile-samples
		*.avi)	filetype_label Microsoft Audio Video Interleaved
			avitype -- "$f"
			;;

		# requires: brotli
		*.br) filetype_label brotli-compressed file
			# This will be slow on large files because it actually decompresses
			brotli -tv -- "$f"
			;;

		# requires: Java jdk
		*.class) filetype_label Java compiled class file
			javap "$sf" | grep -E '(^C)|(\{$)'
			;;

		# requires: openssl
		*.crl) filetype_label X509 CRL
			openssl crl -text -in "$f"
			;;

		# requires: openssl
		*.csr) filetype_label X509 Certificate Signing Request
			openssl req -text -noout -in "$f"
			;;

		# requires: xz
		synthesis.hdlist.cz) filetype_label URPM synthesis file
			printf "synthesis file for %d packages\n" \
				"$(xz -qdc < "$f" | grep -c '^@info@')"
			# Guess this info from the filenames
			printf "primary architecture %s\n" \
				"$(xz -qdc < "$f" | sed -n -Ee 's/^@info@[^@]+\.([^.@]+)@.*$/\1/p' | grep -v 'noarch' | head -1)"
			# This could be the distro tag or a media tag
			TAG="$(xz -qdc < "$f" | sed -n -Ee 's/^@info@[^@]+\.([^.]+)\.[^.]+@.*$/\1/p' | head -1)"
			# Assume it's the distro tag if it has a version number
			if ! printf "%s" "$TAG" | grep -q '[0-9]'; then
				TAG="$(xz -qdc < "$f" | sed -n -Ee 's/^@info@[^@]+\.([^.]+)(\.[^.]+){2}@.*$/\1/p' | head -1).$TAG"
			fi
			printf "distribution tag %s\n" "$TAG"
			;;

		# See http://dar.linux.free.fr/
		# requires: dar >= 2.7.0
		*.dar)	filetype_label Disk Archiver archive
			dar -Q -q -vm -l "$f"
			;;

		# requires: db53-utils
		# This extension is rather generic and often contains a gdbm, sqlite3
		# or some other file.
		*.db) filetype_label Berkeley Database
			db53_stat -d "$f"
			;;

		# requires: tar
		*.depot) filetype_label HP-UX package
			tar xOf - catalog/INDEX < "$f"
			;;

		# requires: tar, gzip
		*.depot.gz) filetype_label HP-UX package
			gzip -dc -- "$f" | tar xOf - catalog/INDEX
			;;

		# requires: openssl
		*.der) filetype_label DER certificate
			openssl x509 -text -inform der -in "$f"
			;;

		# requires: binutils, gzip, tar, xz, zstd
		# On a Debian system, this should use 'dpkg -I "$f"' instead
		# Also OpenMoko package .opk
		*.deb | *.opk)  filetype_label Debian distribution archive
			case "$(ar t -- "$f" control.tar.zst control.tar.xz control.tar.gz 2>/dev/null)" in
				*zst) ar p -- "$f" control.tar.zst | zstd -dc | tar xOf - ./control ;;
				*xz) ar p -- "$f" control.tar.xz | xz -qdc | tar xOf - ./control ;;
				*gz) ar p -- "$f" control.tar.gz | gzip -qdc | tar xOf - ./control ;;
				*) echo Error: unknown deb compression 1>&2 ;;
			esac
			;;

		# needs Android SDK
		*.dex)	filetype_label Android Java class file
			dexlist -- "$f"
			;;

		# hdiutil comes standard on OS X; use qemu-img elsewhere
		# requires: <nothing> || qemu-img
		*.dmg)	filetype_label Mac OS X Disk Image
			hdiutil imageinfo "$sf" || qemu-img info --  "$f"
			;;

		# requires: libdsk-progs
		*.cfi)	filetype_label CFI floppy disk image
			# This type can't be auto-detected so it needs to be specified
			dskid -type cfi -- "$f"
			dsklabel -type cfi "$sf"
			;;

		# requires: libdsk-progs
		*.dsk)	filetype_label Floppy disk image
			dskid -- "$f"
			dsklabel "$sf"
			;;

		# requires: dtc || device-tree-compiler
		*.dtb)	filetype_label Device Tree Binary file
			dtc -I dtb -O dts -o - -- "$f"
			;;

		# Used on macOS
		*.dylib)	filetype_label Mach dynamic library
			otool -h -l -L "$sf"
			;;

		# requires: unzip
		*.egg)	filetype_label Python egg package
			unzip -qp "$sf" EGG-INFO/PKG-INFO
			;;

		# requires: ebook-tools
		*.epub)	filetype_label EPub Document
			einfo "$sf"
			;;


		# requires: pev || pefile (https://pypi.org/project/pefile/)
		# Also Windows PE Dynamic Link Library .dll
		*.exe | *.dll)	filetype_label Windows PE executable
			pescan -v "$sf" || python3 -m pefile "$f"
			;;

		# requires: flac
		*.flac)	filetype_label FLAC audio
			metaflac --list -- "$f"
			;;

		# requires: gdbm
		# These databases are often given a .db extension instead
		*.gdbm)	filetype_label GDBM database
			echo 'set open=readonly;header;count;' | gdbmtool -q -- "$f"
			;;

		# requires: libungif-progs
		*.gif)	filetype_label GIF image
			giftext < "$f"
			;;

		# Also OpenPGP signature
		# requires: gnupg2
		# .sig is used a bit generically, but OpenPGP is probably the most general use
		*.gpg | *.pgp | *.sig)	filetype_label Gnu Privacy Guard/Pretty Good Privacy file
			gpg --list-packets --list-only --no-autostart -- "$f"
			;;

		# See https://pypi.org/project/gpxpy/
		# requires: gpxinfo || gpxpy
		*.gpx)	filetype_label GPS exchange format file
			gpxinfo "$sf"
			;;

		# requires: libheif
		*.heic | *.heif)	filetype_label HEIF image
			heif-info -- "$f"
			;;

		# See http://www.handhelds.org/ (defunct as of 2021)
		# requires: binutils, file, grep, gzip, tar
		*.ipk)	filetype_label Itsy package
			# Detect the old or new style ipk format
			if file -- "$f" | grep -Eiq 'Debian|ar archive' ; then
				ar -p "$sf" control.tar.gz | gzip -dc | tar xOf -
			else
				# Internal file name may or may not be prefixed with ./
				# which is what the \* catches.
				check_tar_wildcards
				gzip -dc -- "$f" | tar xOf - $TAR_WILDCARDS \*control.tar.gz | gzip -dc | tar xOf -
			fi
			;;

		# requires: cdrecord
		*.iso)	filetype_label ISO9660 filesystem image file
			isoinfo -d -i "$f"
			;;

		# requires: unzip
		*.jar) filetype_label Java jar file
			unzip -pqaa "$sf" META-INF/MANIFEST.MF | sed  '/^$/,$d'
			;;

		# requires: system
		*.journal) filetype_label journald log file
			journalctl --header --file "$f"
			;;

		# requires: jhead || exiv2 || exif
		*.jpg | *.jpeg | *.jfif)	filetype_label JPEG image
			jhead "$sf" || exiv2 pr "$sf" || exif -- "$f"
			;;

		# requires: openssl
		*.key) filetype_label Private key
			openssl asn1parse -in "$f"
			;;

		# requires: fv, lzip
		*.lz)	filetype_label lzip-compressed file
			fv -t lz "$f"
			;;

		# requires: fv, lzop
		*.lzo)	filetype_label LZOP-compressed file
			fv -t lzo "$f"
			;;

		# requires: libquicktime-progs
		*.m4a)	filetype_label Quicktime audio
			qtinfo "$f"
			;;

		# requires: gettext
		# requires: sed
		*.mo | *.gmo) filetype_label Binary message catalog file
			msgunfmt -- "$f" | sed '/^$/,$d'
			;;

		# requires: mkvtoolnix
		*.mkv | *.webm) filetype_label Matroska video file
			mkvinfo "$sf"
			;;

		# requires: advancecomp
		*.mng) filetype_label Multiple-image Network Graphics file
			advmng -l -- "$f"
			;;

		# requires: python-id3 || id3ed || mp3info || id3lib || id3v2 || mpgtx
		*.mp3)	filetype_label MP3 audio file
			id3v2 -l "$sf" || listid3v2.py "$sf" || id3ed -i "$sf" || mp3info -- "$f" || id3info -- "$f" || tagmp3 show "$f"
			;;

		# requires: libquicktime-progs
		*.mp4 | *.mpg4 | *.mov | *.qt | *.3gp | *.3gpp)	filetype_label Quicktime video
			qtinfo "$f"
			;;

		# requires: mpgtx
		*.mpg | *.mpeg | *.mpe | *.m1v | *.m2v)	filetype_label MPEG video
			mpginfo -- "$f"
			;;

		# requires: binutils
		*.o | *.a | *.so | *.so.? | *.so.?? | *.so.???)	filetype_label Object file/archive
			objdump -afph -- "$f"
			;;

		# requires: vorbis-tools
		*.ogg | *.ogv)	filetype_label OGG audio/video
			ogginfo -- "$f"
			;;

		# requires: wireshark-tools
		*.pcap | *.pcapng | *.5vw | *.erf | *.trc0 | *.cap | *.tr1 | *.snoop | \
		*.ncf | *.ncfx | *.bfr | *.pcap.gz | *.pcapng.gz | *.5vw.gz | \
		*.erf.gz | *.trc0.gz | *.cap.gz | *.tr1.gz | *.snoop.gz | *.ncf.gz | \
		*.ncfx.gz | *.bfr.gz) filetype_label Pcap network capture file
			capinfos -- "$f"
			;;

		# requires: pilot-link
		*.pdb | *.prc) filetype_label Palm Pilot file
			pilot-file -Ha -- "$f"
			;;

		# requires: xpdf
		*.pdf | *.ai)	filetype_label Portable Document Format document
			pdfinfo -- "$f"
			;;

		# Also X.509 certificate .crt
		# Also X.509 CA certificate .cacrt
		# requires: openssl || java-X-openjdk
		*.pem | *.cacrt | *.crt) filetype_label PEM certificate
			openssl crl2pkcs7 -nocrl -certfile "$f" | openssl pkcs7 -print_certs -text -noout || keytool -printcert -v -file "$f"
			;;

		# requires: pacman
		*.pkg.tar | *.pkg.tar.xz | *.pkg.tar.zst | *.pkg.tar.bz2 | \
		*.pkg.tar.gz)	filetype_label Pacman package
			pacman -Qip "$sf"
			;;

		# requires: pngtools
		*.png)	filetype_label Portable Network Graphics image
			pnginfo "$sf"
			;;

		# requires: sed
		*.po) filetype_label Text message catalog file
			sed '/^$/,$d' < "$f"
			;;

		# requires: netpbm
		*.ppm | *.pbm | *.pgm | *.pam | *.pnm)	filetype_label Portable pixmap image
			pamfile -- "$f"
			;;

		# requires: uncompyle6
		*.pyc | *.pyd | *.pyo)	filetype_label Python compiled file
			uncompyle6 --  "$f" | grep '^#'
			;;

		# requires: qemu-img
		*.qcow | *.qcow2 | *.qed | *.vpc | *.bochs | \
		*.parallels)	filetype_label QEMU disk image
			qemu-img info --  "$f"
			;;

		# requires: unrar
		*.rar | *.rsn)	filetype_label RAR-compressed archive
			unrar vta -sl0 -idc -- "$f"
			;;

		# See http://www.rpm.org/
		# requires: rpm
		*.rpm | *.spm)	filetype_label Red Hat RPM package
			rpm -qip -- "$f"
			;;

		# requires: fv, python
		*.rz)	filetype_label rzip archive
			fv -t rz "$f"
			;;

		# See https://sourceforge.net/projects/sistk/
		# requires: DumpSIS
		*.sis)	filetype_label SymbianOS SIS installable package
			DumpSIS.pl -e -- "$f" | sed '/^Files$/,$d'
			;;

		# See https://github.com/itkach/slob/
		# requires: slob
		*.slob)	filetype_label Sorted List of Blobs dictionary
			slob info -- "$f"
			;;

		# requires: sqlite3-tools
		*.sqlite | *.sqlite3) filetype_label Sqlite database file
			sqlite3 "$sf" .dbinfo "select 'Available Tables:';" .tables
			;;

		# See https://github.com/plougher/squashfs-tools/
		# Also Snap package .snap
		# Also Tiny Core Linux package .tcz
		# requires: squashfs-tools
		*.sqsh | *.squashfs | *.sqfs | *.snap | *.tcz) filetype_label Squashfs filesystem image
			unsquashfs -s "$sf"
			;;

		# requires: python >= 3
		*.stl)	filetype_label STL model file
			# Detect ASCII or binary format
			if [ "$(dd if="$f" bs=1 count=80 2>/dev/null | wc -l)" -gt 0 -a "$(dd if="$f" bs=1 count=6 2>/dev/null | tr -d '\0')" = "solid " -a "$(dd if="$f" bs=1 count=1 skip=80 2>/dev/null | od -An -b)" != "000" ]; then
				# ASCII stl format
				sed -n -E -e '1s/^solid +//p' < "$f" | iconv -f windows-1252
				grep -c "^[[:space:]]*facet " < "$f" | tr -d '\n'
				echo " facets"
			else
				# Binary stl format
				python3 - "$f" <<EOF
import struct, sys
with open(sys.argv[1], 'rb') as f:
	comment = f.read(80)
	z = comment.find(b'\x00')
	if z >= 0:
		comment = comment[0:z]
	comment = comment.strip(b'\n').strip(b'\r').replace(b'\n', b' ').replace(b'\r', b' ').strip(b' ')
	# This character encoding likely isn't always correct
	print(comment.decode('windows-1252'))
	length = struct.unpack('<I', f.read(4))[0]
print('%0d facets' % length)
EOF
			fi
			;;

		# requires: imagemagick, gzip
		*.svgz)	filetype_label Compressed Scalable Vector Graphic image file
			zcat -- "$f" | identify -verbose -
			;;

		# requires: swftools
		*.swf)	filetype_label Shockwave Flash file
			swfdump "$sf"
			;;

		# See http://www.encode.ru/tc/ (defunct as of 2022)
		# requires: python >= 3
		# Note: the real file extension conflicts with Tellico files, so this
		# is a made up one
		*.tc-compressed)	filetype_label tc-compressed file
			python3 - "$f" <<EOF
import struct, sys, os
fn=sys.argv[1]
with open(fn, 'rb') as f:
	length = struct.unpack('<I', f.read(4))[0]
basefn=os.path.basename(fn).rsplit('.')[0]
print('    length filename')
print('%10d %s' % (length, basefn))
EOF
			;;

		*.tc)	filetype_label Tellico database
			printf 'Database name: '
			unzip -pq "$sf" tellico.xml | xmlstarlet sel -t -v "/*[local-name()='tellico']/*[local-name()='collection']/@title" -n 2> /dev/null
			printf 'Number of records: '
			unzip -pq "$sf" tellico.xml | xmlstarlet sel -t -v "count(/*[local-name()='tellico']/*[local-name()='collection']/*[local-name()='entry'])" -n 2>/dev/null
			printf 'Number of images:'
			unzip -pq "$sf" tellico.xml | xmlstarlet sel -t -v "count(/*[local-name()='tellico']/*[local-name()='collection']/*[local-name()='images']/*[local-name()='image'])" -n 2>/dev/null
			echo 'Database fields:'
			unzip -pq "$sf" tellico.xml | xmlstarlet sel -t -m "/*[local-name()='tellico']/*[local-name()='collection']/*[local-name()='fields']/*[local-name()='field']" -o '  ' -v '@name' -n 2>/dev/null
			;;

		# requires: samba-client
		# Note: running as non-root usually results in
		# "ERROR: could not init messaging context" since it needs to create
		# a samba message lock file.
		*.tdb)	filetype_label TDB database file
			printf 'Number of keys in database: '
			dbwrap_tool --persistent --configfile=/dev/null -- "$f" listkeys | wc -l
			;;

		# See https://simonowen.com/samdisk/
		# requires: samdisk
		*.bpb | *.cfi | *.cpm | *.d2m | *.d4m | *.d80 | *.d81 | *.d88 | *.dfi | \
		*.dmk | *.ds2 | *.dsc | *.dsk | *.dti | *.edsk | *.fdi | *.hfe | *.imd | \
		*.ipf | *.lif | *.mbd | *.mgt | *.msa | *.opd | *.raw | *.s24 | \
		*.sad | *.sbt | *.scl | *.scp | *.sdf | *.td0 | *.trd | *.udi \
		)  filetype_label Floppy disk image file
			samdisk info -- "$f"
			;;

		# Also Digital Negative Raw Image .dng
		# requires: libtiff-progs
		*.tif | *.tiff | *.dng)	filetype_label Tagged Image File Format image
			tiffinfo -- "$f" || tiffdump -- "$f"
			;;

		# requires: aria2 || bittorrent
		*.torrent)	filetype_label BitTorrent torrent file
			aria2c --show-file -- "$sf" || torrentinfo-console "$sf"
			;;

		# requires: openssl
		*.tsr) filetype_label X509 Time Stamp Protocol Reply
			openssl ts -reply -text -in "$f"
			;;

		# requires: gobject-introspection, xmlstarlet
		*.typelib)	filetype_label G-IR binary database
			g-ir-generate -- "$f" | xmlstarlet sel -N x=http://www.gtk.org/introspection/core/1.0 -t -o 'Typelib for ' -v '/x:repository/x:namespace/@name' -o ' ' -v '/x:repository/x:namespace/@version' -o ' ' -v '/x:repository/x:namespace/@shared-library' -n  -
			;;

		# .vmdk is actually a VMWare disk image, and .vhd{,x} are Microsoft formats
		# but VirtualBox supports them as well.
		# requires: virtualbox || qemu-img
		*.vdi | *.vmdk | *.vhd | *.vhdx)	filetype_label VirtualBox disk image
			VBoxManage showmediuminfo -- "$f" || qemu-img info --  "$f"
			;;

		*.warc.gz)	filetype_label Compressed Web Archive Collection file
			gzip -dc -- "$f" | tr -d '\015' | sed -n -e 1d -e '2,/^$/{p;b' -e '}' -e p -e '/^$/q'
			;;

		# requires: wavplay
		*.wav)	filetype_label WAV audio file
			wavplay -i -- "$f"
			;;

		# requires: unzip, jq
		*.wacz)	filetype_label Web Archive Collection Zipped
			unzip -pq "$sf" datapackage.json | jq 'del(.resources)'
			;;

		# requires: unzip
		*.whl)	filetype_label Python wheel package
			unzip -pq "$sf" '*.dist-info/METADATA' | sed '/^$/,$d'
			;;


		# zip archive .zip
		# Also Android archive library .aar
		# Also Android package .apk
		# Also Apkmirror Android package .apkm
		# Also Mozilla Java Cross Platform Installer .xpi
		# Also Perl package .par
		# Also Winamp compressed skin file .wsz,.wal
		# Also Google Earth .kmz
		# Also iPod Game .ipg
		# Also OpenOffice Impress Presentation .sxi
		# Also OpenOffice Drawing .sxd
		# Also OpenOffice Math .sxm
		# Also OpenOffice Writer Document .sxw
		# Also OpenDocument .odt
		# Also OpenDocument Drawing .odg
		# Also OpenDocument Spreadsheet .ods
		# Also OpenDocument Presentation .odp
		# Also OpenDocument Formula .odf
		# Also OpenDocument Drawing Template .otg
		# Also OpenDocument Presentation Template .otp
		# Also OpenDocument Spreadsheet Template .ots
		# Also OpenDocument Presentation Template .otp
		# Also OpenDocument Extension .oxt
		# Also Quake3 packed file .pk3
		# Also Nokia mobile phone theme .nth
		# Also Microsoft Office Open XML .docx
		# Also Microsoft Office Open XML with Macros .docm
		# Also Microsoft Office Open XML .pptx
		# Also Microsoft Office Open XML .xlsx
		# Also EPub Document .epub
		# Also Java Web Start .war
		# Also XML Paper Specification .xps
		# Also Syllable resource package .resource
		# Also Syllable application package .application
		# Also (unknown 3D package) .f3d
		# Also Krita image .kra
		# Also Scratch Project .sb2
		# Also Scratch Project .sb3
		# Also MuseScore music score .mscz
		# SkyOS .pkg file are in .zip format, but the extension conflicts
		# requires: unzip
		*.zip | *.jar | *.xpi | *.par | *.wsz | *.wal | *.kmz | \
		*.sxi | *.sxd | *.sxw | *.sxm | *.pk3 | *.ipg | *.nth | *.odt | \
		*.ods | *.odp | *.odg | *.odf | *.oxt | \
		*.otp | *.otg | *.ots | \
		*.docx | *.pptx | *.xlsx | *.epub | *.apk | *.war | *.xps | \
		*.resource | *.application | *.docm | *.f3d | *.kra | \
		*.sb2 | *.sb3 | *.apkm | *.mscz | *.aar)
			filetype_label ZIP archive
			zipinfo -h -z "$sf" | grep -v "^$"; zipinfo -l "$sf" | tail -1
			;;

# Catch-all handlers. These must appear after the rest because they include
# more general extensions and occasionally duplicate but less-desirable
# handlers of previous extensions.

		# Catch-all bitmapped image information displayer
		# requires: imagemagick
		*.raw | *.pcl | *.xpm | *.jbig | *.jp2 | *.png | *.fpx | *.pcx | \
		*.xbm | *.webp | *.heic | *.cur \
		)	filetype_label Bitmapped image file
			identify -verbose -- "$f"
			;;

		# Also Windows Media compressed skin file .wmz
		# Catch-all vector information displayer
		# requires: imagemagick
		*.ps | *.eps | *.hpgl | *.fig | *.cgm | *.gplt | \
		*.rle | *.wmf | *.wmz | *.svg \
		)	filetype_label Vector image file
			identify -verbose -- "$f"
			;;

		# Catch-all font information displayer
		# requires: freetype2-demos
		*.ttf | *.pfa | *.pfb | *.otf | *.woff \
		)	filetype_label Font file
			ftdump "$sf"
			;;

		# Catch-all video and audio formats
		# requires: ffmpeg
		# First batch of extensions come from "ffmpeg -formats"
		# which lists some things that probably aren't actual file extensions
	*.3g2 | *.3gp | *.4xm | *.MTV | *.RoQ | *.aac | *.ac3 | *.adts | *.aiff | \
	*.alaw | *.amr | *.apc | *.ape | *.asf | *.asf_stream | *.au | *.avi | \
	*.avs | *.bethsoftvid | *.c93 | *.crc | *.daud | *.dirac | *.dsicin | *.dts | \
	*.dv | *.dv1394 | *.dvd | *.dxa | *.ea | *.ffm | *.film_cpk | *.flac | *.flic | \
	*.flv | *.framecrc | *.gif | *.gxf | *.h261 | *.h263 | *.h264 | *.idcin | \
	*.image2 | *.image2pipe | *.ingenient | *.ipmovie | *.libnut | *.m4v |\
	*.matroska | *.mjpeg | *.mm | *.mmf | *.mov | *.m4a | \
	*.mj2 | *.mp2 | *.mp3 | *.mp4 | *.mpc | *.mpeg | *.mpeg1video | \
	*.mpeg2video | *.mpegts | *.mpegtsraw | *.mpegvideo | *.mpjpeg | *.mulaw |\
	*.mxf | *.nsv | *.null | *.nut | *.nuv | *.ogg | *.oss | *.psp | *.psxstr | \
	*.rawvideo | *.redir | *.rm | *.rtp | *.rtsp | *.s16be | *.s16le | *.s8 | \
	*.sdp | *.shn | *.siff | *.smk | *.sol | *.svcd | *.swf | *.thp | \
	*.tiertexseq | *.tta | *.txd | *.u16be | *.u16le | *.u8 | *.vc1 | \
	*.vcd | *.video4linux | *.video4linux2 | *.vmd | *.vob | *.voc | \
	*.wav | *.wc3movie | *.wsaud | *.wsvqa | *.wv | *.x11grab | *.yuv4mpegpipe | \
	\
	*.mpg4 | *.wmv | *.mts | *.qt | \
	*.amv | *.ram \
		)	filetype_label Video or audio file
			ffprobe "$f" || ffmpeg -i "$f"
			;;

# Plain compressed files must come after otherwise compressed files

		# requires: fv, bzip2
		# Also Stampede Linux package .slp
		# Also bzip2-compressed tar archive .tbz,.tbz2
		*.slp | *.tbz | *.tbz2 | \
		*.bz2)	filetype_label bzip2-compressed file
			fv -t bz2 "$f"
			;;

		# requires: fv, gzip
		*.cgz | *.pet | \
		*.nif | *.epk | *.qpk | *.qpr | *.fpm | *.rub | \
		*.kpr | *.chrt | *.kfo | *.flw | *.kil | *.ksp | *.kwd | \
		*.tgz | *.taz | \
		*.gz | *.Z | *.z)	filetype_label gzip-compressed file
			fv -t gz "$f"
			;;

		# requires: fv, lzma
		# Also Tukaani Linux package .tlz
		*.tlz | \
		*.lzma)	filetype_label LZMA-compressed file
			fv -t lzma "$f"
			;;

		# requires: fv, xz
		# Also Midnight BSD ports package .mport
		# Also xz-compressed tar archive .txz
		*.txz | *.mport | \
		*.xz)	filetype_label XZ-compressed file
			fv -t xz "$f"
			;;

		# requires: fv, zstd
		*.zst)	filetype_label zstd-compressed file
			fv -t zst "$f"
			;;

		# Unknown format
		# requires: file
		*)	echo "$filetype: Unsupported file format." 1>&2
			file -- "$f" 1>&2
			# Indicate that an error has occurred
			false
			;;

	esac
	if [ "$FV_VERBOSE" = 2 ] ; then
	  set +x
	fi
	# Save the return code of the last program run so we can return that from
	# the script. This won't work for some of the metadata programs, but it's
	# better than nothing.
	rc=$?
	echo ""
done
exit "$rc"
