Clean-up KDE Thumbnails

metadata

KDE creates thumbnails of all your files when you view them in Dolphin or Konqueror. It then saves these thumbnails so that if you look at the photo again it doesn’t have to regenerate the thumbnail. This is a really efficient way of doing things. Plus, it stores the thumbnails in a central place instead of, like Windows, leaving little Thumbs.db files around the place. The drawback with the current system is that if you delete a photo then its thumbnail is not deleted along with it. This means that if you simply rename a folder then a huge swathe of saved thumbnails will no longer refer to the correct files - thus being useless. After a while these accumulate and occupy disk space when they really shouldn’t.

Version 2

This version is completely unrelated to the first one - it is simply a BASH script that relies on exiftool to do the inspection of the PNG meta-data. This is the one that I run regularly on my Linux boxes. I will leave the old version here too for archival.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

#!/usr/bin/env bash

# This script is copyrighted to Thomas Guymer and is licensed under the
# Creative Commons Attribution Non-Commercial 2.0 UK: England & Wales Licence.

# This script can be run in two ways:
# * type "bash script.sh" into a console window; or
# * type "./script.sh" into a console window.

# This script will:
#   * search for all the saved thumbnails on your KDE system; then
#   * check if the original file is present and delete the thumbnail if it is
#     not.

# Check that the required software is installed ...
exiftool=$(which exiftool 2> /dev/null)
if [[ ! -x $exiftool ]]; then
    echo "ERROR: \"exiftool\" is not installed." >&2
    exit 1
fi

# Define function ...
urldecode() {
    # NOTE: https://unix.stackexchange.com/a/187256
    local url_encoded="${1//+/ }"
    printf '%b' "${url_encoded//%/\\x}"
}

# Initialize counters and kernel type ...
kern=$(uname)
count=0
total=0

# Loop over directories (if present) ...
for dir in "$HOME/.thumbnails"/*; do
    [[ ! -d "$dir" ]] && continue

    echo "Removing surplus thumbnails from \"$dir\" ..."

    # Loop over PNGs (if present) ...
    for png in "$dir"/*.png; do
        [[ ! -f "$png" ]] && continue

        # Extract URI tag (skip if not present) and extract URI from tag string
        # (skipping thumbnails for SFTP files) ...
        uri=$($exiftool -ThumbURI "$png" 2> /dev/null)
        [[ ${#uri} -eq 0 ]] && continue
        uri=${uri##* }
        [[ ${uri:0:7} == "sftp://" ]] && continue

        # Decide how to handle the URI ...
        if [[ ${uri:0:7} == "file://" ]]; then
            # Decode the URI, trim the URI and skip if the original file still
            # exists ...
            uri=$(urldecode "$uri")
            uri=${uri:7}
            [[ -f "$uri" ]] && continue

            # Increment counters and remove thumbnail ...
            count=$((count + 1))
            case $kern in
                Darwin)
                    total=$((total + $(stat -f %z "$png")))
                    ;;
                FreeBSD)
                    total=$((total + $(stat -f %z "$png")))
                    ;;
                Linux)
                    total=$((total + $(stat -c %s "$png")))
                    ;;
                *)
                    echo "ERROR: Unknown kernel type." >&2
                    exit 1
            esac
            rm "$png"
        else
            echo "ERROR: An unknown URI type has been found." >&2
            echo "       Thumbnail => $png" >&2
            echo "       URI       => $uri" >&2
            exit 1
        fi
    done
done

echo "Removed $count thumbnails which totaled $((total / 1024 / 1024)) MiB."

              
You may also download “cleanup-kde-thumbnails.sh” directly or view “cleanup-kde-thumbnails.sh” on GitHub Gist (you may need to manually checkout the “main” branch).

Version 1

To quote the comment in the script:

This script will: search for all the saved thumbnails on your KDE system; then check if the original file is present and delete the thumbnail if it is not; or delete the thumbnail if it references the trash can.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116

#!/usr/bin/php
<?php
// This script is copyrighted to Thomas Guymer and is licensed under the
// Creative Commons Attribution Non-Commercial 2.0 UK: England & Wales Licence.

// This script can be run in two ways:
// * type "php systemThumbnails.php" into a console window; or
// * type "./systemThumbnails.php" into a console window.

// This script will:
//   * search for all the saved thumbnails on your KDE system; then
//   * check if the original file is present and delete the thumbnail if it is
//     not; or
//   * delete the thumbnail if it references the trash can.

$username = "user1";

// DO NOT EDIT BELOW THIS LINE

// Search for 'normal' thumbnails
echo " > Searching for thumbnails ... ";
$folderPath = "/home/" . $username . "/.thumbnails/normal";
if($handle = opendir($folderPath)) {
    while(false !== ($file = readdir($handle))) {
        if($file !== "." && $file !== "..") {
            $contents[] = $folderPath . "/" . $file;
        }
    }
    closedir($handle);
}
// Search for 'large' thumbnails
$folderPath = "/home/" . $username . "/.thumbnails/large";
if($handle = opendir($folderPath)) {
    while(false !== ($file = readdir($handle))) {
        if($file !== "." && $file !== "..") {
            $contents[] = $folderPath . "/" . $file;
        }
    }
    closedir($handle);
}
// Start checking thumbnails
if(!empty($contents)) {
    sort($contents);
    echo "done. (There are " . count($contents) . ")\n";
    echo "   > Processing ... ";
    $size = 0;
    $counter = 0;
    foreach($contents as $key => $value) {
        $file = file_get_contents($value);
        if(stripos($file, "Thumb::URI") !== false && stripos($file, "IDAT") !== false) {
            if(stripos($file, "tEXtThumb::URI") !== false) {
                // Find chunk length
                $integer = substr($file, stripos($file, "Thumb::URI")-8, 4);
                // Collect data
                $start = stripos($file, "Thumb::URI") + strlen("Thumb::URI") + 1; // This skips the null byte, as defined in the PNG specification https://www.w3.org/TR/2003/REC-PNG-20031110/#11tEXt
                $length = ord(substr($integer, 0, 1)) + ord(substr($integer, 1, 1)) + ord(substr($integer, 2, 1)) + ord(substr($integer, 3, 1)) - strlen("Thumb::URI") - 1;
                unset($integer);
                $original = substr($file, $start, $length);
                unset($start, $length);
                // Decode
                $original = utf8_encode($original);
                $original = urldecode($original);
            }
            elseif(stripos($file, "zTXtThumb::URI") !== false) {
                // Find chunk length
                $integer = substr($file, stripos($file, "Thumb::URI")-8, 4);
                // Collect data
                $start = stripos($file, "Thumb::URI") + strlen("Thumb::URI") + 2; // This skips special characters, as defined in the PNG specification https://www.w3.org/TR/2003/REC-PNG-20031110/#11zTXt
                $length = ord(substr($integer, 0, 1)) + ord(substr($integer, 1, 1)) + ord(substr($integer, 2, 1)) + ord(substr($integer, 3, 1)) - strlen("Thumb::URI") - 2;
                unset($integer);
                $original = substr($file, $start, $length);
                unset($start, $length);
                // Uncompress
                $original = gzuncompress($original);
                // Decode
                $original = utf8_encode($original);
                $original = urldecode($original);
            }
            else {
                echo "\nERROR: The meta data in " . $value . " is not in a recognised format.\n";
                exit(1);
            }
            // Check if the file can be deleted
            if(stripos($original, "file://") === 0) {
                // Remove bumf
                $original = substr($original, strlen("file://"));
                // Check if original exists
                if(!file_exists($original)) {
                    $size = $size + filesize($value);
                    $counter = $counter + 1;
                    unlink($value);
                }
            }
            elseif(stripos($original, "trash:/") === 0) {
                $size = $size + filesize($value);
                $counter = $counter + 1;
                unlink($value);
            }
            else {
                echo "\nERROR: " . $original . " is not a local file. (" . $value . ")\n";
            }
            unset($original);
        }
        else {
            echo "\nERROR: " . $value . " does not contain correct meta data.\n";
        }
        unset($file);
    }
    $size = round($size/(1024*1024),1);
    echo "done.\n";
    echo " > " . $counter . " thumbnails were deleted which totaled " . $size . "MB.\n";
}
else {
    echo "done. (There are no thumbnails on your system.)\n";
}
?>

              
You may also download “cleanup-kde-thumbnails.php” directly or view “cleanup-kde-thumbnails.php” on GitHub Gist (you may need to manually checkout the “main” branch).

I hope this script is useful for you. The first time I ran it it removed 90 MBs of redundant thumbnails for me!