среда, 22 августа 2012 г.

Linux. How to delete duplicate images 2?

Для создания и поддержания в актуальном виде коллекции изображений нужно иметь инструмент, позволяющий находить и удалять дубликаты. В свое время я пытался сделать такой инструмент.

Но подход, который я использовал требует значительной производительности и на больших коллекциях маленькая утилита разрастается достаточно большую программу со свое базой данных с интерфейсом и всеми прелестями программного проекта.

Более эффективно воспользоваться существующими инструментами, например мощной программой управления фотоколлекцией DigiKam.

Но штука в том, что DigiKam имеет очень мощный инструмент поиска дубликатов, не имеет возможности работы с дубликатами, кроме как мышкой и клавиатурой.

Я провел небольшое расследование и обнаружил, что результаты поиска дубликатов сохраняются в базе данных, которая обычно располагается в $HOME/Pictures/digikam4.db

Как DigiKam сохраняет результаты поиска я покажу на следующем примере.

Есть папка с изображениями.
В программе digikam запускаем поиск одинаковых изображений.

Результаты поиска сохраняются в таблице searches. Эта таблица содержит строки с результатами поиска.


Каждая строка в поле query хранит xml список содержащий id одинаковых изображений. На приведенном листинге я отформатировал xml, чтобы удобней его было читать.


 
  
   3
   2 
   1
  
 


Имена файлов изображений хранятся в таблице images и доступны по id


Путь доступен по полю (номер альбома) в таблице albums:


Путь к альбомам хранится в таблице albumroot:


Ниже пример реализации функции на bash получения пути изображения с id = 5
#!/bin/bash 

DBPATH=digikam4.db 

getPicture () 
{ 
  FILENAME=$(sqlite3 $DBPATH "SELECT name FROM images WHERE id=$1") 
  IDALBUM=$(sqlite3 $DBPATH "SELECT album FROM images WHERE id=$1") 
  REALTIVEPATH=$(sqlite3 $DBPATH "SELECT relativePath FROM albums WHERE id=$IDALBUM") 
  IDALBUMROOT=$(sqlite3 $DBPATH "SELECT albumRoot FROM albums WHERE id=$IDALBUM") 
  ROOTPATH=$(sqlite3 $DBPATH "SELECT specificPath FROM albumroots WHERE id=$IDALBUMROOT") 
  echo /home/${ROOTPATH}/${REALTIVEPATH}/${FILENAME} 
} 

FNAME=$(getPicture 5) 
echo $FNAME 

Ну, собственно, вот и венец творений.
Программа на bash, которая удаляет дубликаты изображений.
Программа удаляет дубликат меньшего размера.
Я ее успешно использую совместно с DigiKam 2.8 под Ubuntu.
Перед запуском скрипта нужно выйти из DigiKam, иначе скрипт не может получить доступ к базе, которая лочится.
#!/bin/bash
#-------------------------------------------------------------------
# Удаляет дубликаты картинок на основании поиска дубликатов в 
# программе DigiKam
#
# Проверено с DigiKam v2.8
COPYRUGHT="\n© http://axa-ru.blogspot.com. Licensed under GPLv3."

#-------------------------------------------------------------------
#VERSION=0.1 # initial version
#VERSION=0.11 # Some DeBugs
#VERSION=0.12 # Добавлено приоритет удаления
#VERSION=0.13 # Some changes
VERSION=0.14 # Испрвавление ошибки в delSmallPic()

DEBUG=0
VERBOSE=1
DBPATH=$HOME/Pictures/digikam4.db # Путь до базы даннх DigiKam
EXITERROR=192

SAVEPIC=""
PATTERN=0_WallPapers99

# Color ANSY sequence
BLACK="\033[0;30m"
RED="\033[0;31m"
GREEN="\033[0;32m"
YELLOW="\033[0;33m"
BLUE="\033[0;34m"
MAGENTA="\033[0;35m"
CYAN="\033[0;36m"
WHITE="\033[0;37m"
GRAY1="\033[38;5;252m"
GRAY2="\033[38;5;245m"
GRAY3="\033[38;5;238m"
NOCOLOR="\033[0;38m"

#-------------------------------------------------------------------
#  Возвращает по id изображения его полный путь
#  пример использования:
#     FNAME=$(getPicture 1653)
#  возвращает путь к картинке №1653

getPicture ()
{
  FILENAME=$(sqlite3 $DBPATH "SELECT name FROM images WHERE id=$1")
  IDALBUM=$(sqlite3 $DBPATH "SELECT album FROM images WHERE id=$1")
  REALTIVEPATH=$(sqlite3 $DBPATH "SELECT relativePath FROM albums WHERE id=$IDALBUM")
  IDALBUMROOT=$(sqlite3 $DBPATH "SELECT albumRoot FROM albums WHERE id=$IDALBUM")
  local ALBUM_TYPE=$(sqlite3 $DBPATH "SELECT type FROM albumroots WHERE id=$IDALBUMROOT")
  case $ALBUM_TYPE in
    1) ROOTPATH=/home$(sqlite3 $DBPATH "SELECT specificPath FROM albumroots WHERE id=$IDALBUMROOT");;
    2) echo "External media not supported yet. Exiting"; exit $EXITERROR;;
    3) ROOTPATH=$(sqlite3 $DBPATH "SELECT identifier FROM albumroots WHERE id=$IDALBUMROOT" | sed 's/networkshareid:?mountpath=//g' | sed 's/%2F/\//g');;
  esac  

  echo ${ROOTPATH}/${REALTIVEPATH}/${FILENAME}
}

#-------------------------------------------------------------------
#  Удаляет меньшую картинку из двух
#  
delSmallPic()
{
  ((DEBUG)) && echo "    ***** $FUNCNAME"

  if [ "$SAVEPIC" == "" ]; then 
    #((DEBUG)) && echo "    $LINENO: Init SAVEPIC"
    SAVEPIC="$1";
  else
    local W0=$(identify -format '%w' "$SAVEPIC" 2> /dev/null)
    local H0=$(identify -format '%h' "$SAVEPIC" 2> /dev/null)
    local W1=$(identify -format '%w' "$1" 2> /dev/null)
    local H1=$(identify -format '%h' "$1" 2> /dev/null)

    ((DEBUG)) && echo "    $LINENO: PIC1=$SAVEPIC"
    ((DEBUG)) && echo "    $LINENO: PIC2=$1"
    ((DEBUG)) && echo "    $LINENO: $W0 -eq $W1 -a $H0 -eq $H1"

    # Если картинки одинаковы то удаляет ту, в пути (имени)
    # которой есть $PATTERN
    if [ $W0 -eq $W1 -a $H0 -eq $H1 ]; then
      ((DEBUG)) && echo "    $LINENO: PIC1 == PIC2"
      if [[ "$SAVEPIC" =~ .*$PATTERN.* ]]; then
        echo "Delete $SAVEPIC"
        ((DEBUG)) && printf "    %s: " $LINENO
        ! ((DEBUG)) && rm "$SAVEPIC"
        SAVEPIC="$1"
      else
        ((DEBUG)) && printf "    %s: " $LINENO
        echo "Delete $1"
        ! ((DEBUG)) && rm "$1"
      fi
    else 
      if [ $W0 -le $W1 -o $H0 -le $H1 ]; then
        ((DEBUG)) && echo "    $LINENO: PIC1 <= PIC2"
        echo "Delete $SAVEPIC"
        ((DEBUG)) && printf "    %s: " $LINENO
        ! ((DEBUG)) && rm "$SAVEPIC"
        SAVEPIC="$1"
      else
        ((DEBUG)) && echo "    $LINENO: PIC1 > PIC2"
        ((DEBUG)) && printf "    %s: " $LINENO
        echo "Delete $1"
        ! ((DEBUG)) && rm "$1"
      fi    
    fi
  fi
}

#-------------------------------------------------------------------
#  Удаляет дубликаты картинок из списка
#  В качестве параметра получает список id картинок с разделителями пробелами
delDupPic ()
{
  while (( "$#" )); do
    local PAR=$1
    PICURL=$(getPicture "$PAR")
    if [ ! -f $PICURL ]; then
      echo -e "${RED}File $PICURL das not exists.${NOCOLOR}"
      # echo -e "${RED}Use digiKam!!!${NOCOLOR}"
      # exit $EXITERROR
    fi
    ((DEBUG)) && echo "  $LINENO: PAR=$PAR, PICURL=$PICURL"
    delSmallPic $PICURL
    shift
  done
}

#-------------------------------------------------------------------
#  main code
#

  while getopts "Dd:hVv" SWITCH ; do
    case $SWITCH in
      D) DEBUG=1;;
      d) if [[ $OPTARG == "" ]]; then 
           printf "$SCRIPT:$LINENO: must be argument\n"; 
           exit $ERROR; 
         else 
           PATTERN=$OPTARG; 
         fi;;
      h) echo -e "usage: 
  ${CYAN}$0 [-OPTION]${NOCOLOR}

    OPTION is
    ${CYAN}D${NOCOLOR}       - Debug On
    ${CYAN}d [PAT]${NOCOLOR} - Delete first pic by PATtern
                Example: 
                 deldup.sh -d 00_Picture 
                will delete any picture
                 /mnt/nas3d0/Pictures/Wallpaper/00_Pictures/Pictures_0123.jpg 
    ${CYAN}h${NOCOLOR}       - This help
    ${CYAN}v${NOCOLOR}       - Verbose output, No output by default"
        exit 0
        ;;
      V) echo -e "${CYAN}$0 Version $VERSION\n${COPYRUGHT}${NOCOLOR}"; exit 0;;
      v) VERBOSE=1;;
      *) printf "$SCRIPT:$LINENO: script error: unhandled argument\n"; exit $ERROR;;
    esac
  done


((DEBUG)) && echo "$LINENO: TOTAL=$TOTAL"

# Type=7 - записи после операции поиска дубликатов
X=$(sqlite3 $DBPATH "SELECT id FROM searches where type=7 LIMIT 1")
TOTAL=$(sqlite3 $DBPATH "SELECT COUNT (*) FROM searches WHERE type=7")
echo "$TOTAL Duplicate Images Will be Delete "
((LAST=TOTAL+X))
((DEBUG)) && echo "$LINENO: X=$X, LAST=$LAST, TOTAL=$TOTAL"

while [ $X -lt $LAST ]
do
  SAVEPIC=""
  # Получаем список записей с дублированными картинками 
  # Ниже показан отформатированный пример. 
  # 
  # 
  #   
  #     
  #       5033
  #       201
  #       84132
  #     
  #   
  # 
  LISTEQPIC=$(sqlite3 $DBPATH "SELECT query FROM searches WHERE id=$X" | sed 's/<\/listitem>/,/g' | sed 's///g' | sed 's/.*oneof\">//' | sed 's/,<\/.*//' | sed 's/,/ /g' )
  ((DEBUG)) && echo "$LINENO: X=$X, LISTEQPIC=$LISTEQPIC"
  delDupPic $LISTEQPIC
  ((X++))
done

По хорошему этот скрипт можно оформить в виде плагина в DigiKam и запускать оттуда. Будет функциональней и удобней. Но мне лень ковырять сам DigiKam. Если кто сделает такое добро, то отпишите.


Полезные ссылки: