Fix caching errors.

master
Jason A. Donenfeld 2011-05-05 18:37:15 -04:00
parent ff5aac5bad
commit 4e02abb7a3
5 changed files with 85 additions and 42 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.pyc *.pyc
cache/* cache/*
test/*

30
CachePath.py 100644
View File

@ -0,0 +1,30 @@
import os.path
from datetime import datetime
def set_cache_path_base(base):
trim_base.base = base
def untrim_base(path):
return os.path.join(trim_base.base, path)
def trim_base_custom(path, base):
if path.startswith(base):
path = path[len(base):]
if path.startswith('/'):
path = path[1:]
return path
def trim_base(path):
return trim_base_custom(path, trim_base.base)
def cache_base(path):
path = trim_base(path).replace('/', '-').replace(' ', '_')
if len(path) == 0:
path = "root"
return path
def json_cache(path):
return cache_base(path) + ".json"
def image_cache(path, size, square=False):
if square:
suffix = str(size) + "s"
else:
suffix = str(size)
return cache_base(path) + "_" + suffix + ".jpg"
def file_mtime(path):
return datetime.fromtimestamp(int(os.path.getmtime(path)))

View File

@ -1,31 +1,10 @@
from CachePath import *
from datetime import datetime from datetime import datetime
import json import json
import os.path import os.path
from PIL import Image from PIL import Image
from PIL.ExifTags import TAGS from PIL.ExifTags import TAGS
def set_cache_path_base(base):
trim_base.base = base
def untrim_base(path):
return os.path.join(trim_base.base, path)
def trim_base_custom(path, base):
if path.startswith(base):
path = path[len(base):]
if path.startswith('/'):
path = path[1:]
return path
def trim_base(path):
return trim_base_custom(path, trim_base.base)
def cache_base(path):
path = trim_base(path).replace('/', '-').replace(' ', '_')
if len(path) == 0:
path = "root"
return path
def json_cache(path):
return cache_base(path) + ".json"
def image_cache(path, suffix):
return cache_base(path) + "_" + suffix + ".jpg"
class Album(object): class Album(object):
def __init__(self, path): def __init__(self, path):
self._path = trim_base(path) self._path = trim_base(path)
@ -34,6 +13,12 @@ class Album(object):
self._photos_sorted = True self._photos_sorted = True
self._albums_sorted = True self._albums_sorted = True
@property @property
def photos(self):
return self._photos
@property
def albums(self):
return self._albums
@property
def path(self): def path(self):
return self._path return self._path
def __str__(self): def __str__(self):
@ -101,10 +86,15 @@ class Album(object):
return None return None
class Photo(object): class Photo(object):
thumb_sizes = [ (150, True), (640, False), (1024, False) ]
def __init__(self, path, thumb_path=None, attributes=None): def __init__(self, path, thumb_path=None, attributes=None):
self._path = trim_base(path) self._path = trim_base(path)
self.is_valid = True self.is_valid = True
mtime = datetime.fromtimestamp(os.path.getmtime(path)) try:
mtime = file_mtime(path)
except:
self.is_valid = False
return
if attributes is not None and attributes["DateTimeFile"] >= mtime: if attributes is not None and attributes["DateTimeFile"] >= mtime:
self._attributes = attributes self._attributes = attributes
return return
@ -123,6 +113,8 @@ class Photo(object):
info = image._getexif() info = image._getexif()
except: except:
return return
if not info:
return
for tag, value in info.items(): for tag, value in info.items():
decoded = TAGS.get(tag, tag) decoded = TAGS.get(tag, tag)
if not isinstance(decoded, int) and decoded not in ['JPEGThumbnail', 'TIFFThumbnail', 'Filename', 'FileSource', 'MakerNote', 'UserComment', 'ImageDescription', 'ComponentsConfiguration']: if not isinstance(decoded, int) and decoded not in ['JPEGThumbnail', 'TIFFThumbnail', 'Filename', 'FileSource', 'MakerNote', 'UserComment', 'ImageDescription', 'ComponentsConfiguration']:
@ -135,13 +127,9 @@ class Photo(object):
pass pass
self._attributes[decoded] = value self._attributes[decoded] = value
def _thumbnail(self, image, thumb_path, size, square=False): def _thumbnail(self, image, thumb_path, size, square=False):
if square: thumb_path = os.path.join(thumb_path, image_cache(self._path, size, square))
suffix = str(size) + "s"
else:
suffix = str(size)
thumb_path = os.path.join(thumb_path, image_cache(self._path, suffix))
print "Thumbing %s" % thumb_path print "Thumbing %s" % thumb_path
if os.path.exists(thumb_path) and datetime.fromtimestamp(os.path.getmtime(thumb_path)) >= self._attributes["DateTimeFile"]: if os.path.exists(thumb_path) and file_mtime(thumb_path) >= self._attributes["DateTimeFile"]:
return return
image = image.copy() image = image.copy()
if square: if square:
@ -157,7 +145,10 @@ class Photo(object):
bottom = image.size[1] - ((image.size[1] - image.size[0]) / 2) bottom = image.size[1] - ((image.size[1] - image.size[0]) / 2)
image = image.crop((left, top, right, bottom)) image = image.crop((left, top, right, bottom))
image.thumbnail((size, size), Image.ANTIALIAS) image.thumbnail((size, size), Image.ANTIALIAS)
image.save(thumb_path, "JPEG") try:
image.save(thumb_path, "JPEG")
except:
os.path.unlink(thumb_path)
def _thumbnails(self, image, thumb_path): def _thumbnails(self, image, thumb_path):
if "Orientation" in self._attributes: if "Orientation" in self._attributes:
@ -186,16 +177,20 @@ class Photo(object):
elif orientation == 8: elif orientation == 8:
# Rotation 90 # Rotation 90
mirror = image.transpose(Image.ROTATE_90) mirror = image.transpose(Image.ROTATE_90)
self._thumbnail(mirror, thumb_path, 150, True) for size in Photo.thumb_sizes:
self._thumbnail(mirror, thumb_path, 640) self._thumbnail(mirror, thumb_path, size[0], size[1])
self._thumbnail(mirror, thumb_path, 1024)
@property @property
def name(self): def name(self):
return os.path.basename(self._path) return os.path.basename(self._path)
def __str__(self): def __str__(self):
return self.name return self.name
@property @property
def image_caches(self):
return [image_cache(self._path, size[0], size[1]) for size in Photo.thumb_sizes]
@property
def date(self): def date(self):
if not self.is_valid:
return datetime(1900, 1, 1)
if "DateTimeOriginal" in self._attributes: if "DateTimeOriginal" in self._attributes:
return self._attributes["DateTimeOriginal"] return self._attributes["DateTimeOriginal"]
elif "DateTime" in self._attributes: elif "DateTime" in self._attributes:

View File

@ -1,16 +1,17 @@
import os import os
import os.path import os.path
from datetime import datetime from datetime import datetime
from PhotoAlbum import Photo, Album, json_cache, set_cache_path_base from PhotoAlbum import Photo, Album
from CachePath import json_cache, set_cache_path_base, file_mtime
class TreeWalker: class TreeWalker:
def __init__(self, album_path, cache_path): def __init__(self, album_path, cache_path):
self.album_path = album_path self.album_path = os.path.abspath(album_path)
self.cache_path = cache_path self.cache_path = os.path.abspath(cache_path)
set_cache_path_base(self.album_path) set_cache_path_base(self.album_path)
self.all_albums = list() self.all_albums = list()
self.all_photos = list() self.all_photos = list()
self.walk(album_path) self.walk(self.album_path)
self.remove_stale() self.remove_stale()
def walk(self, path): def walk(self, path):
print "Walking %s" % path print "Walking %s" % path
@ -20,10 +21,12 @@ class TreeWalker:
if os.path.exists(cache): if os.path.exists(cache):
print "Has cache %s" % path print "Has cache %s" % path
cached_album = Album.from_cache(cache) cached_album = Album.from_cache(cache)
if os.path.getmtime(path) <= os.path.getmtime(cache): if file_mtime(path) <= file_mtime(cache):
print "Album is fully cached" print "Album is fully cached"
cached = True cached = True
album = cached_album album = cached_album
for photo in album.photos:
self.all_photos.append(photo)
if not cached: if not cached:
album = Album(path) album = Album(path)
for entry in os.listdir(path): for entry in os.listdir(path):
@ -34,12 +37,12 @@ class TreeWalker:
cache_hit = False cache_hit = False
if cached_album: if cached_album:
cached_photo = cached_album.photo_from_path(entry) cached_photo = cached_album.photo_from_path(entry)
if cached_photo and datetime.fromtimestamp(os.path.getmtime(entry)) <= cached_photo.attributes["DateTimeFile"]: if cached_photo and file_mtime(entry) <= cached_photo.attributes["DateTimeFile"]:
print "Photo cache hit %s" % entry print "Photo cache hit %s" % entry
cache_hit = True cache_hit = True
photo = cached_photo photo = cached_photo
if not cache_hit: if not cache_hit:
print "No cache - scanning %s" % entry print "No cache, scanning %s" % entry
photo = Photo(entry, self.cache_path) photo = Photo(entry, self.cache_path)
if photo.is_valid: if photo.is_valid:
self.all_photos.append(photo) self.all_photos.append(photo)
@ -49,4 +52,18 @@ class TreeWalker:
self.all_albums.append(album) self.all_albums.append(album)
return album return album
def remove_stale(self): def remove_stale(self):
pass #TODO: remove left over caches for cache in os.listdir(self.cache_path):
match = False
for album in self.all_albums:
if cache == album.cache_path:
match = True
break
if match:
continue
for photo in self.all_photos:
if cache in photo.image_caches:
match = True
break
if not match:
print "Removing stale cache %s" % cache
os.unlink(os.path.join(self.cache_path, cache))

View File

@ -2,4 +2,4 @@
from TreeWalker import TreeWalker from TreeWalker import TreeWalker
walker = TreeWalker("/home/zx2c4/Pictures", "./cache") walker = TreeWalker("./test", "./cache")