Valid XHTML Icon Valid CSS Icon Valid SVG Icon Unicode UTF-8 Icon
Add to del.icio.us Digg this! Post to reddit Share on Facebook Add to StumbleUpon Add to Google Bookmarks

Clean-up KDE System Thumbnails

Creative Commons LogoThis work is licensed under the Creative Commons Attribution Non-Commercial 2.0 UK: England & Wales Licence. This means that you are free to: copy; distribute; and modify this work. It also means that you cannot use it for commercial purposes. Additionally, you must attribute this work to the original author, Thomas Guymer, ideally with a link.

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.

#!/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: http://unix.stackexchange.com/a/187256/133865
	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."

Version 1

To quote the comment in the script:

This script will:

  1. search for all the saved thumbnails on your KDE system; then
  2. check if the original file is present and delete the thumbnail if it is not; or
  3. delete the thumbnail if it references the trash can.
#!/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 http://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 http://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";
}
?>

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

This page was last modified on 21/02/2016.

The Unfriendly Web

Map of total firewall denials on my servers

Update (September 2015)

This site has changed quite a lot since I bought the domain in November 2005 and first started hosting my own content. During that time the internet has evolved an awful lot; as I write this in 2015 it is almost unrecognisable to what it once was. Gone are the days of hosting your own photo albums from your holidays on your own website: now you create an album on Facebook to share with your friends and family. Got some special photos that you are particularly proud of? Then deviantART or Flickr are the places for you to showcase them. Found an interesting page and wish to share it with your friends? Twitter and Facebook will update them immediately wherever they are. Written some pieces of source code that you think other people might find useful? GitHub will version track and syntax highlight it in an instant.

Consequently, this site no longer has photo albums and panoramas taken from my travels: the special ones are in my deviantART gallery and the normal ones are on my private Facebook page. I don't have a WordPress blog at the minute so I will still keep my articles on fixing technological problems ("Releases" and "Tutorials") on here for archival - if they're useful to you then that's great. If I ever restart publishing code it'll be on my GitHub page.

I have learned a lot since I first started writing (non-public) web pages in 2002. As testimony, this site: does not set any cookies; barely has any JavaScript on it; and is no longer dynamically generated using PHP. Rather, it is completely static with updates propagated using make every midnight thanks to cron.

© 2002-2017 Thomas Guymer. See the Copyright Statement.