#!/usr/bin/env bash ############################################################################## # # books_search.sh # By Steven Saus # (c) 2020; licensed under the MIT license # # Find and open an ebook from your Calibre library without, ahem, opening your # Calibre library. Feeds in format, author, and title to fzf / rofi for you # to choose, then uses epy/epr/conversion for TUI/CLI and xdg-open for GUI. # # Originally inspired by a script by [Miroslav Vidovic](https://github.com/miroslavvidovic/rofi-scripts) # though rewritten, expanded, and changed quite a freaking lot. # # Requires Calibre books be stored in the default structure of # /AUTHOR/TITLE (NUBMER)/BOOKFILE # # For example, an example is # /home/steven/documents/Calibre Library/Douglas Adams/So Long, and Thanks for All the Fish (362)/So Long, and Thanks for All the Fish - Douglas Adams.epub # # Creates a flat file "database" on first run in $HOME/.cache/book_search_cache # which can be regenerated (cronjob, etc) by running this script with the -e # switch. # # You can also regenerate it immediately before a run by using the -r switch # # Use -m to utilize ebook metadata instead of the file structure mentioned above. # # add in subject from exiftool (may need to add cpan install Activity::Zip (I think?) for tags # # # Why wasn't I using this??? # calibredb list -f authors,formats,cover,tags,series,series_index,title --separator § # id is first row, so can run multiple times to attach id to each of these. Or maybe feed into # memory dynamically for fzf? That's probably better, innit? # calibredb list -f authors,formats,cover,tags,series,series_index,title --separator § # # need check for multi-format # # preview window for cover and/or summary? #With Preview #SelectedFile=$(cat "$CacheFile" | fzf --no-hscroll -m --height 80% --border --ansi --no-bold --preview="$SCRIPTDIR/quite-intriguing-preview {}" | sed 's/ (/./g' | sed 's/)//g' | sed 's/:man:/:man -Pcat:/g' | awk -F ':' '{print $2 " " $1}' # #calibredb list -f title,authors | fzf +x -e -i --header-lines=2 --preview="notify-send {}" # # use both so can use whatever backend, even without calibre at all. ############################################################################## # Books directory BOOKS_DIR="/home/steven/documents/Calibre Library/" EXIFTOOL=$(which exiftool) FD_FIND=$(which fdfind) EPY=$(which epy) #https://github.com/wustho/epy EPR=$(which epr) #https://github.com/wustho/epr declare -a FILES REGEN="false" CliOnly="true" CacheFile="$HOME/.cache/book_search_cache" CALIBRE_STRUCTURE="true" # So that I don't have to worry about the structure of the path too hard SLASHES=$(echo "$BOOKS_DIR" | grep -o '/' | wc -l) ((SLASHES++)) gen_list (){ # I guess this could be done with a do-while loop and process substitution # instead, but I'm on call and it's 0318 and I don't want to tear it apart. if [ -f "$FD_FIND" ];then mapfile FILES < <(fdfind -a -e epub -e pdf -e mobi -e azw3 . "$BOOKS_DIR") else mapfile FILES < <(find -H "$BOOKS_DIR" -type f -iname "*.pdf" -or -iname "*.epub" -or -iname "*.mobi" -or -iname "*.azw3") fi echo "" > $CacheFile if [ "$CALIBRE_STRUCTURE" == "true" ];then for ((i = 0; i < ${#FILES[@]}; i++));do FILES[$i]=$(echo "${FILES[$i]}" | tr -d '\n') shortfile=$(basename "${FILES[$i]}") EXT=${shortfile##*.} PATH0=$(dirname "${FILES[$i]}") PATH1=$(echo "$PATH0" | cut -d '/' -f ${SLASHES}- ) #echo "$PATH1 @" AUTHOR=$(echo "$PATH1" | awk -F '/' '{print $1}') TITLE=$(echo "$PATH1" | awk -F '/' '{print $2}' | awk -F '(' '{print $1}') printf "%s | %s | %s | %s \n" "${EXT}" "${AUTHOR}" "${TITLE}" "${FILES[$i]}" >> $CacheFile done else for ((i = 0; i < ${#FILES[@]}; i++));do metadata="" FILES[$i]=$(echo "${FILES[$i]}" | tr -d '\n') metadata=$(exiftool "${FILES[$i]}") AUTHOR=$(echo "$metadata" | grep -e "^Creator" | grep -v "Creator Id" | awk -F ':' '{print $2}'| xargs) TITLE=$(echo "$metadata" | grep -e "^Title" | grep -v "Title Id" | awk -F ':' '{print $2}'| xargs) EXT=$(echo "$metadata" | grep -e "^File Type Extension" | awk -F ':' '{print $2}' | xargs) printf "%s | %s | %s | %s \n" "${EXT}" "${AUTHOR}" "${TITLE}" "${FILES[$i]}" >> $CacheFile done fi } main() { if [ "$REGEN" != "false" ];then gen_list fi if [ ! -f $CacheFile ];then gen_list fi if [ "$CliOnly" == "true" ];then SelectedBook=$(cat $CacheFile | fzf --no-hscroll -m --height 60% --border --ansi --no-bold --header "Which Book?" ) else #use ROFI, not zenity SelectedBook=$(cat $CacheFile | rofi -i -dmenu -p "Which Book?" -theme DarkBlue) fi #extra xargs to strip newlines and whitespace book=$( echo "$SelectedBook" | awk -F '|' '{print $4}' | xargs) type=$( echo "$SelectedBook" | awk -F '|' '{print $1}' | xargs) if [ -n "$book" ]; then if [ "$CliOnly" == "false" ];then xdg-open "${book}" else if [ ! -f "$EPY" ] && [ ! -f "$EPR" ];then # there is no sense doing extra processing if xdg-open is just # going to handle it. xdg-open \""${book}"\" else case "$type" in pdf) tmpfile=$(mktemp) ## Pick the one you like! pdftotext -nopgbrk -layout "${book}" "$tmpfile"; bat "$tmpfile"; rm "$tmpfile" #pdftohtml -c -i -s -zoom 1 "${book}" "$tmpfile"; www-browser "$tmpfile"; rm "$tmpfile" exit 0 ;; mobi|azw3) tmpdir=$(mktemp -d) # You hopefully have calibre? ebook-convert "${book}" "$tmpdir/out.epub" book=$(echo "$tmpdir/out.epub") ;; *) echo "Calling reader" ;; esac if [ -f "$EPY" ];then executeme=$(printf "%s \"%s\"" "$EPY" "$book") eval "$executeme" elif [ -f "$EPR" ];then executeme=$(printf "%s \"%s\"" "$EPR" "$book") eval "$executeme" fi fi fi else echo "Book file not found!" 1>&2 exit 1 fi } ############################################################################## # Show the Help ############################################################################## display_help(){ echo "###################################################################" echo "# booksearch.sh [-h|-r]" echo "# -h show help " echo "# -r regenerate booklist (and run) " echo "# -e regenerate booklist (and exit) " echo "# -g use GUI (rofi) " echo "# -m Use ebook metadata, not file structure" echo "# FAIR WARNING: PDF METADATA IS OFTEN BORKED BEYOND RECOGNITION!" echo "###################################################################" } # Read in variables while [ $# -gt 0 ]; do option="$1" case $option in -m) CALIBRE_STRUCTURE="false" shift ;; -h) display_help exit shift ;; -r) REGEN="true" shift ;; -e) gen_list exit shift ;; -g) CliOnly="false" shift ;; esac done main