img-chon.sh 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. #!/bin/bash
  2. IMG_BASE="${IMG_BASE:-$HOME}"
  3. DATABASE="${DATABASE:-$HOME/Pictures/image_tags.sqlite}"
  4. SQLITE="${SQLITE:-sqlite3}"
  5. SQL="$SQLITE $DATABASE"
  6. SXIV="sxiv -at -"
  7. THUMBNAIL_OPTS="-resize 100x -quality 75"
  8. join_by () {
  9. local IFS="$1"
  10. shift
  11. echo "$*"
  12. }
  13. sp="/-\|"
  14. sc=0
  15. spin() {
  16. printf "\r%s [%s/%s] %s" "${sp:sc++:1}" "$1" "$2" "$3"
  17. if ((sc==${#sp})); then
  18. sc=0
  19. fi
  20. }
  21. endspin() {
  22. printf "\r\n"
  23. }
  24. get_hash() {
  25. [ -f "$1" ] || exit 1
  26. read -ra sha < <(sha256sum "$1")
  27. echo "${sha[0]}"
  28. }
  29. case $1 in
  30. "init")
  31. $SQL "CREATE TABLE image (
  32. path TEXT NOT NULL UNIQUE ON CONFLICT REPLACE,
  33. hash TEXT PRIMARY KEY NOT NULL UNIQUE ON CONFLICT ABORT,
  34. thumbnail TEXT
  35. );"
  36. $SQL "CREATE TABLE image_tag (
  37. hash TEXT NOT NULL,
  38. tag TEXT NOT NULL,
  39. UNIQUE (hash, tag) ON CONFLICT ABORT
  40. );"
  41. $SQL "CREATE TRIGGER image_exists
  42. BEFORE INSERT ON image_tag
  43. BEGIN
  44. SELECT CASE
  45. WHEN (SELECT COUNT(*) FROM image WHERE image.hash = NEW.hash) = 0 THEN
  46. RAISE(FAIL, 'image not in database')
  47. END;
  48. END;"
  49. ;;
  50. "add")
  51. total="${#@}"
  52. i=0
  53. for f in "${@:2}"; do
  54. spin "$i" "$total" "$f"
  55. i=$((i+1))
  56. hsh="$(get_hash "$f")"
  57. path="$(realpath --relative-to="$IMG_BASE" "$f")"
  58. thumbnail="$(convert "$f" ${THUMBNAIL_OPTS[@]} jpg:- | base64 --wrap 0)"
  59. $SQL "INSERT INTO image (path, hash, thumbnail) VALUES ('$path', '$hsh', '$thumbnail');"
  60. done
  61. endspin
  62. ;;
  63. "tag")
  64. tag=$2
  65. for f in "${@:3}"; do
  66. hsh="$(get_hash "$f")"
  67. $SQL "INSERT INTO image_tag (hash, tag) VALUES ('$hsh','$tag');"
  68. done
  69. ;;
  70. "untag")
  71. tag=$2
  72. for f in "${@:3}"; do
  73. hsh="$(get_hash "$f")"
  74. $SQL "DELETE FROM image_tag WHERE (hash, tag) = ('$hsh','$tag');"
  75. done
  76. ;;
  77. query|query-thumb)
  78. tags=()
  79. anti_tags=()
  80. for t in "${@:2}"; do
  81. if [[ "$t" == -* ]]; then
  82. anti_tags+=( "'${t:1}'" )
  83. else
  84. tags+=( "'$t'" )
  85. fi
  86. done
  87. tags_str=$(join_by , "${tags[@]}")
  88. anti_tags_str=$(join_by , "${anti_tags[@]}")
  89. if [ "$1" == "query-thumb" ]; then
  90. query="thumbnail"
  91. else
  92. query="path"
  93. fi
  94. sql_query="SELECT $query FROM image WHERE ${#tags[@]} = (SELECT COUNT(*) FROM image_tag WHERE image.hash = image_tag.hash AND image_tag.tag IN ($tags_str)) AND (0 = (SELECT COUNT(*) FROM image_tag WHERE image.hash = image_tag.hash AND image_tag.tag IN ($anti_tags_str))) ORDER BY image.path;"
  95. if [ "$1" == "query-thumb" ]; then
  96. $SQL "$sql_query"
  97. else
  98. my_base="$(realpath --relative-to=. $IMG_BASE)"
  99. $SQL "$sql_query" | awk "{ print \"$my_base/\" \$0 }"
  100. fi
  101. ;;
  102. "thumbnail")
  103. $SQL "SELECT thumbnail FROM image WHERE hash = '$2'"
  104. ;;
  105. "tags")
  106. if [ -f "$2" ]; then
  107. hsh="$(get_hash "$2")"
  108. $SQL "SELECT DISTINCT tag FROM image_tag WHERE hash = '$hsh' ORDER BY tag;"
  109. else
  110. $SQL "SELECT DISTINCT tag FROM image_tag ORDER BY tag;"
  111. fi
  112. ;;
  113. "query-dmenu")
  114. selected_tags=()
  115. while selection=$($0 tags | dmenu -p "Select tags, <esc> when done.") && [ -n "$selection" ]; do
  116. selected_tags+=("$selection")
  117. done
  118. $0 query "${selected_tags[@]}" | $SXIV
  119. ;;
  120. "tag-dmenu")
  121. while selection=$($0 tags | dmenu -p "Select tags, <esc> when done.") && [ -n "$selection" ]; do
  122. $0 tag "$selection" "${@:2}"
  123. done
  124. ;;
  125. "stats")
  126. echo "Number of images: $($SQL "SELECT COUNT(*) FROM image;")"
  127. echo "Number of distinct tags: $($SQL "SELECT COUNT(DISTINCT tag) FROM image_tag;")"
  128. echo "Database size: $(du -h "$DATABASE" | awk '{ print $1 }')"
  129. ;;
  130. **)
  131. name="$(basename "$0")"
  132. echo -e "Usage:"
  133. echo -e " $name init Create a new database."
  134. echo -e " $name add IMAGE... Add new image(s) to the database."
  135. echo -e " $name tag TAG IMAGE... Tag image(s) with TAG."
  136. echo -e " $name untag TAG IMAGE... Remove tag from image(s)."
  137. echo -e " $name query TAG... List images with specified tags."
  138. echo -e " $name tags [IMAGE] Show all tags, or tags for IMAGE."
  139. echo -e " $name query-dmenu Interactively construct a query using dmenu and opens the result in sxiv."
  140. echo -e " $name tag-dmenu IMAGE... Interactively tag specified image(s) using dmenu."
  141. echo -e " $name stats Show database statistics."
  142. ;;
  143. esac