I've been modifying the vifm-sixel-preview script to also display some info below the image preview, but I'm having trouble truncating CJK text, as i won't be able to properly count characters. It does seems like vifm itself is able to handle this, though. Any clues on how I can properly adapt my script? Thanks in advance! Love your work, I'm a huge vifm fan!
#!/bin/bash
# Ensure the cache directory exists
[ -d "$HOME/.cache/vifm" ] || mkdir -p "$HOME/.cache/vifm"
# Parameters
action="$1"
panel_width="$2"
panel_height="$3"
cell_width=7
cell_height=17
width=$((panel_width * cell_width))
height=$((panel_height * cell_height))
image_file="$4"
background=none
# Cache path
PCACHE="$HOME/.cache/vifm/thumbnail.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$PWD/$image_file")" | sha256sum | awk '{print $1}')"
# Function to calculate available pane width
calculate_pane_width() {
echo "$panel_width"
}
# Function to check dependencies
check_dependencies() {
local missing=()
for dep in "$@"; do
if ! command -v "$dep" &> /dev/null; then
missing+=("$dep")
fi
done
if [ ${#missing[@]} -ne 0 ]; then
echo "Missing dependencies: ${missing[*]}"
exit 1
fi
}
# Cleanup function
cleanup() {
printf '\33[s\33[5A\33[2K\33[u'
clear
exit 0
}
# Image function: Resizes image to fit within given dimensions without borders
image() {
magick "$1" -background "$background" -resize "${2}x${3}" -gravity center -extent "${2}x${3}" sixel:-
}
# Function to get video information and format it appropriately
get_video_info() {
local file="$1"
local pane_width=$(calculate_pane_width)
local max_info_width=$((pane_width * cell_width))
local resolution=$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 "$file" 2>/dev/null)
local duration_seconds=$(ffprobe -v error -select_streams v:0 -show_entries format=duration -of csv=p=0 "$file" 2>/dev/null | awk '{printf "%.0f", $1}')
# Calculate hours, minutes, and seconds
local hours=$((duration_seconds / 3600))
local minutes=$(( (duration_seconds % 3600) / 60 ))
local seconds=$((duration_seconds % 60))
# Format duration with conditional hours
if [ $hours -gt 0 ]; then
local duration=$(printf "%02d:%02d:%02d" $hours $minutes $seconds)
else
local duration=$(printf "%02d:%02d" $minutes $seconds)
fi
local video_codec=$(ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of csv=p=0 "$file" 2>/dev/null)
local audio_codec=$(ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of csv=p=0 "$file" 2>/dev/null)
local fps=$(ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of csv:p=0 "$file" 2>/dev/null | awk -F'/' '{if ($2==0) print $1; else print $1/$2}')
# Format the information
local info="$resolution | $duration | $video_codec | $audio_codec | ${fps%.*}fps"
# Calculate the maximum number of characters that can fit in the pane width
local max_chars=$((max_info_width / cell_width))
# Truncate output if it exceeds max_chars
if [ $(echo "$info" | wc -m) -gt $max_chars ]; then
info=$(echo "$info" | cut -c 1-$((max_chars - 3)))"..."
fi
echo "$info"
}
# Function to get audio information and format it appropriately
get_audio_info() {
local file="$1"
local pane_width=$(calculate_pane_width)
local max_info_width=$((pane_width * cell_width))
# Extract embedded album art if available
local album_art_cache="$HOME/.cache/vifm/album_art_$(basename "$file").jpg"
if [ ! -f "$album_art_cache" ]; then
ffmpeg -i "$file" -an -vcodec copy -f image2 "$album_art_cache" >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Error extracting album art."
exit 1
fi
fi
# Display album art as preview
image "$album_art_cache" "$width" "$height"
# Format duration
local duration_seconds=$(ffprobe -v error -select_streams a:0 -show_entries format=duration -of csv=p=0 "$file" 2>/dev/null | awk '{printf "%.0f", $1}')
local hours=$((duration_seconds / 3600))
local minutes=$(( (duration_seconds % 3600) / 60 ))
local seconds=$((duration_seconds % 60))
local duration=$(printf "%02d:%02d:%02d" $hours $minutes $seconds)
# Get audio codec and bitrate
local audio_codec=$(ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of csv=p=0 "$file" 2>/dev/null)
local bitrate=$(ffprobe -v error -select_streams a:0 -show_entries format=bit_rate -of csv=p=0 "$file" 2>/dev/null | awk '{printf "%.0f kbps", $1 / 1000}')
# Get artist, title, album, and year
local artist=$(ffprobe -v error -show_entries format_tags=artist -of csv=p=0 "$file" 2>/dev/null)
local title=$(ffprobe -v error -show_entries format_tags=title -of csv=p=0 "$file" 2>/dev/null)
local album=$(ffprobe -v error -show_entries format_tags=album -of csv=p=0 "$file" 2>/dev/null)
local year=$(ffprobe -v error -show_entries format_tags=date -of csv=p=0 "$file" 2>/dev/null)
# Format the information
local info="$duration | $audio_codec | $bitrate | $artist - $title ($album) - $year"
# Calculate the maximum number of characters that can fit in the pane width
local max_chars=$((max_info_width / cell_width))
# Truncate output if it exceeds max_chars
if [ $(echo "$info" | wc -m) -gt $max_chars ]; then
info=$(echo "$info" | cut -c 1-$((max_chars - 3)))"..."
fi
echo "$info"
}
# Function to generate audio spectrum (currently disabled)
generate_audio_thumbnail() {
# Uncomment and modify as needed if you want to generate an audio spectrum
# local file="$1"
# ffmpeg -i "$file" -filter_complex "[0:a]showspectrum=s=640x480:mode=combined:color=intensity:slide=scroll" -frames:v 1 "${PCACHE}.jpg"
return # Disable function, no spectrum generation
}
# Validate input
if [[ -z "$action" || -z "$panel_width" || -z "$panel_height" || -z "$image_file" ]]; then
echo "Usage: $0 <action> <panel_width> <panel_height> <image_file>"
exit 1
fi
# Check dependencies based on action
case "$action" in
"video")
check_dependencies ffmpegthumbnailer ffprobe
;;
"epub")
check_dependencies epub-thumbnailer
;;
"pdf")
check_dependencies pdftoppm
;;
"audio")
check_dependencies ffmpeg ffprobe
# generate_audio_thumbnail "$image_file" # Uncomment this line if you want to re-enable audio spectrum generation
;;
"font")
check_dependencies fontpreview
;;
*)
check_dependencies magick
;;
esac
# Perform action based on input
case "$action" in
"clear")
cleanup
;;
*)
# Display preview
case "$action" in
"draw")
image "$image_file" "$width" "$height"
;;
"video")
# Generate thumbnail if not cached
if [ ! -f "${PCACHE}.jpg" ]; then
if ! ffmpegthumbnailer -i "$image_file" -o "${PCACHE}.jpg" -s 0 -q 5; then
echo "Error generating video thumbnail for $image_file"
exit 1
fi
fi
# Display thumbnail
image "${PCACHE}.jpg" "$width" "$height"
# Display video information
get_video_info "$image_file"
;;
"epub")
[ ! -f "${PCACHE}.jpg" ] && epub-thumbnailer "$image_file" "$PCACHE" 1024
image "${PCACHE}.jpg" "$width" "$height"
;;
"pdf")
[ ! -f "${PCACHE}.jpg" ] && pdftoppm -jpeg -f 1 -singlefile "$image_file" "$PCACHE"
image "${PCACHE}.jpg" "$width" "$height"
;;
"audio")
# Extract album art if not cached
get_audio_info "$image_file"
;;
"font")
[ ! -f "${PCACHE}.jpg" ] && fontpreview -i "$image_file" -o "${PCACHE}.jpg"
image "${PCACHE}.jpg" "$width" "$height"
;;
*)
echo "Unknown action: $action"
exit 1
;;
esac
;;
esac