Make thumbnails.
This commit is contained in:
		@ -6,6 +6,8 @@ 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(path):
 | 
			
		||||
	if path.startswith(trim_base.base):
 | 
			
		||||
		path = path[len(trim_base.base):]
 | 
			
		||||
@ -41,7 +43,7 @@ class Album(object):
 | 
			
		||||
	def date(self):
 | 
			
		||||
		self._sort()
 | 
			
		||||
		if len(self._photos) == 0 and len(self._albums) == 0:
 | 
			
		||||
			return datetime.min
 | 
			
		||||
			return datetime(1900, 1, 1)
 | 
			
		||||
		elif len(self._photos) == 0:
 | 
			
		||||
			return self._albums[-1].date
 | 
			
		||||
		elif len(self._albums) == 0:
 | 
			
		||||
@ -77,7 +79,7 @@ class Album(object):
 | 
			
		||||
	def from_dict(dictionary, cripple=True):
 | 
			
		||||
		album = Album(dictionary["path"])
 | 
			
		||||
		for photo in dictionary["photos"]:
 | 
			
		||||
			album.add_photo(Photo.from_dict(photo, album.path))
 | 
			
		||||
			album.add_photo(Photo.from_dict(photo, untrim_base(album.path)))
 | 
			
		||||
		if not cripple:
 | 
			
		||||
			for subalbum in dictionary["albums"]:
 | 
			
		||||
				album.add_album(Album.from_dict(subalbum), cripple)
 | 
			
		||||
@ -90,28 +92,36 @@ class Album(object):
 | 
			
		||||
		else:
 | 
			
		||||
			subalbums = self._albums
 | 
			
		||||
		return { "path": self.path, "date": self.date, "albums": subalbums, "photos": self._photos }
 | 
			
		||||
	def photo_from_path(self, path):
 | 
			
		||||
		for photo in self._photos:
 | 
			
		||||
			if trim_base(path) == photo._path:
 | 
			
		||||
				print "cache hit %s" % path
 | 
			
		||||
				return photo
 | 
			
		||||
		return None
 | 
			
		||||
	
 | 
			
		||||
class Photo(object):
 | 
			
		||||
	def __init__(self, path, attributes=None):
 | 
			
		||||
	def __init__(self, path, thumb_path=None, attributes=None):
 | 
			
		||||
		self._path = trim_base(path)
 | 
			
		||||
		self.is_valid = True
 | 
			
		||||
		mtime = datetime.fromtimestamp(os.path.getmtime(path))
 | 
			
		||||
		if attributes is not None and attributes["FileTime"] >= mtime:
 | 
			
		||||
		if attributes is not None and attributes["DateTimeFile"] >= mtime:
 | 
			
		||||
			self._attributes = attributes
 | 
			
		||||
			return
 | 
			
		||||
		self._attributes = {}
 | 
			
		||||
		self._attributes["FileTime"] = mtime
 | 
			
		||||
		self._attributes["DateTimeFile"] = mtime
 | 
			
		||||
		
 | 
			
		||||
		try:
 | 
			
		||||
			i = Image.open(path)
 | 
			
		||||
			image = Image.open(path)
 | 
			
		||||
		except:
 | 
			
		||||
			self.is_valid = False
 | 
			
		||||
			return
 | 
			
		||||
		self._metadata(image)
 | 
			
		||||
		self._thumbnails(image, thumb_path)
 | 
			
		||||
	def _metadata(self, image):
 | 
			
		||||
		try:
 | 
			
		||||
			info = i._getexif()
 | 
			
		||||
			info = image._getexif()
 | 
			
		||||
		except:
 | 
			
		||||
			info = None
 | 
			
		||||
		if info:
 | 
			
		||||
			return
 | 
			
		||||
		for tag, value in info.items():
 | 
			
		||||
			decoded = TAGS.get(tag, tag)
 | 
			
		||||
			if not isinstance(decoded, int) and decoded not in ['JPEGThumbnail', 'TIFFThumbnail', 'Filename', 'FileSource', 'MakerNote', 'UserComment', 'ImageDescription', 'ComponentsConfiguration']:
 | 
			
		||||
@ -123,13 +133,63 @@ class Photo(object):
 | 
			
		||||
						except:
 | 
			
		||||
							pass			
 | 
			
		||||
				self._attributes[decoded] = value
 | 
			
		||||
	def _thumbnail(self, image, thumb_path, size, square=False):
 | 
			
		||||
		if square:
 | 
			
		||||
			suffix = str(size) + "s"
 | 
			
		||||
		else:
 | 
			
		||||
			suffix = str(size)
 | 
			
		||||
		thumb_path = os.path.join(thumb_path, image_cache(self._path, suffix))
 | 
			
		||||
		if os.path.exists(thumb_path) and datetime.fromtimestamp(os.path.getmtime(thumb_path)) >= self._attributes["DateTimeFile"]:
 | 
			
		||||
			return
 | 
			
		||||
		image = image.copy()
 | 
			
		||||
		if square:
 | 
			
		||||
			if image.size[0] > image.size[1]:
 | 
			
		||||
				left = (image.size[0] - image.size[1]) / 2
 | 
			
		||||
				top = 0
 | 
			
		||||
				right = image.size[0] - ((image.size[0] - image.size[1]) / 2)
 | 
			
		||||
				bottom = image.size[1]
 | 
			
		||||
			else:
 | 
			
		||||
				left = 0
 | 
			
		||||
				top = (image.size[1] - image.size[0]) / 2
 | 
			
		||||
				right = image.size[0]
 | 
			
		||||
				bottom = image.size[1] - ((image.size[1] - image.size[0]) / 2)
 | 
			
		||||
			image = image.crop((left, top, right, bottom))
 | 
			
		||||
		image.thumbnail((size, size), Image.ANTIALIAS)
 | 
			
		||||
		image.save(thumb_path, "JPEG")
 | 
			
		||||
		print "saving %s" % thumb_path
 | 
			
		||||
		
 | 
			
		||||
	def _thumbnails(self, image, thumb_path):
 | 
			
		||||
		orientation = self._attributes["Orientation"]
 | 
			
		||||
		mirror = image
 | 
			
		||||
		if orientation == 2:
 | 
			
		||||
			# Vertical Mirror
 | 
			
		||||
			mirror = image.transpose(Image.FLIP_LEFT_RIGHT)
 | 
			
		||||
		elif orientation == 3:
 | 
			
		||||
			# Rotation 180
 | 
			
		||||
			mirror = image.transpose(Image.ROTATE_180)
 | 
			
		||||
		elif orientation == 4:
 | 
			
		||||
			# Horizontal Mirror
 | 
			
		||||
			mirror = image.transpose(Image.FLIP_TOP_BOTTOM)
 | 
			
		||||
		elif orientation == 5:
 | 
			
		||||
			# Horizontal Mirror + Rotation 270
 | 
			
		||||
			mirror = image.transpose(Image.FLIP_TOP_BOTTOM).transpose(Image.ROTATE_270)
 | 
			
		||||
		elif orientation == 6:
 | 
			
		||||
			# Rotation 270
 | 
			
		||||
			mirror = image.transpose(Image.ROTATE_270)
 | 
			
		||||
		elif orientation == 7:
 | 
			
		||||
			# Vertical Mirror + Rotation 270
 | 
			
		||||
			mirror = image.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_270)
 | 
			
		||||
		elif orientation == 8:
 | 
			
		||||
			# Rotation 90
 | 
			
		||||
			mirror = image.transpose(Image.ROTATE_90)
 | 
			
		||||
		self._thumbnail(mirror, thumb_path, 100, True)
 | 
			
		||||
		self._thumbnail(mirror, thumb_path, 640)
 | 
			
		||||
		self._thumbnail(mirror, thumb_path, 1024)
 | 
			
		||||
	@property
 | 
			
		||||
	def name(self):
 | 
			
		||||
		return os.path.basename(self._path)
 | 
			
		||||
	def __str__(self):
 | 
			
		||||
		return self.name
 | 
			
		||||
	def cache_path(self, size):
 | 
			
		||||
		return image_cache(self.path, size)
 | 
			
		||||
	@property
 | 
			
		||||
	def date(self):
 | 
			
		||||
		if "DateTimeOriginal" in self._attributes:
 | 
			
		||||
@ -137,7 +197,7 @@ class Photo(object):
 | 
			
		||||
		elif "DateTime" in self._attributes:
 | 
			
		||||
			return self._attributes["DateTime"]
 | 
			
		||||
		else:
 | 
			
		||||
			return self._attributes["FileTime"]
 | 
			
		||||
			return self._attributes["DateTimeFile"]
 | 
			
		||||
	def __cmp__(self, other):
 | 
			
		||||
		return cmp(self.date, other.date)
 | 
			
		||||
	@property
 | 
			
		||||
@ -148,6 +208,12 @@ class Photo(object):
 | 
			
		||||
		del dictionary["date"]
 | 
			
		||||
		path = os.path.join(basepath, dictionary["name"])
 | 
			
		||||
		del dictionary["name"]
 | 
			
		||||
		for key, value in dictionary.items():
 | 
			
		||||
			if key.startswith("DateTime"):
 | 
			
		||||
				try:
 | 
			
		||||
					dictionary[key] = datetime.strptime(dictionary[key], "%a %b %d %H:%M:%S %Y")
 | 
			
		||||
				except:
 | 
			
		||||
					pass
 | 
			
		||||
		return Photo(path, dictionary)
 | 
			
		||||
	def to_dict(self):
 | 
			
		||||
		photo = { "name": self.name, "date": self.date }
 | 
			
		||||
@ -157,7 +223,7 @@ class Photo(object):
 | 
			
		||||
class PhotoAlbumEncoder(json.JSONEncoder):
 | 
			
		||||
	def default(self, obj):
 | 
			
		||||
		if isinstance(obj, datetime):
 | 
			
		||||
			return obj.isoformat()
 | 
			
		||||
			return obj.strftime("%a %b %d %H:%M:%S %Y")
 | 
			
		||||
		if isinstance(obj, Album) or isinstance(obj, Photo):
 | 
			
		||||
			return obj.to_dict()
 | 
			
		||||
		return json.JSONEncoder.default(self, obj)
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import os
 | 
			
		||||
import os.path
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from PhotoAlbum import Photo, Album, json_cache, set_cache_path_base
 | 
			
		||||
 | 
			
		||||
class TreeWalker:
 | 
			
		||||
@ -14,17 +15,27 @@ class TreeWalker:
 | 
			
		||||
	def walk(self, path):
 | 
			
		||||
		cache = os.path.join(self.cache_path, json_cache(path))
 | 
			
		||||
		cached = False
 | 
			
		||||
		if os.path.exists(cache) and os.path.getmtime(path) <= os.path.getmtime(cache):
 | 
			
		||||
			album = Album.from_cache(cache)
 | 
			
		||||
		cached_album = None
 | 
			
		||||
		if os.path.exists(cache):
 | 
			
		||||
			cached_album = Album.from_cache(cache)
 | 
			
		||||
			if os.path.getmtime(path) <= os.path.getmtime(cache):
 | 
			
		||||
				cached = True
 | 
			
		||||
		else:
 | 
			
		||||
				album = cached_album
 | 
			
		||||
		if not cached:
 | 
			
		||||
			album = Album(path)
 | 
			
		||||
		for entry in os.listdir(path):
 | 
			
		||||
			entry = os.path.join(path, entry)
 | 
			
		||||
			if os.path.isdir(entry):
 | 
			
		||||
				album.add_album(self.walk(entry))
 | 
			
		||||
			elif not cached and os.path.isfile(entry):
 | 
			
		||||
				photo = Photo(entry)
 | 
			
		||||
				cache_hit = False
 | 
			
		||||
				if cached_album:
 | 
			
		||||
					cached_photo = cached_album.photo_from_path(entry)
 | 
			
		||||
					if cached_photo and datetime.fromtimestamp(os.path.getmtime(entry)) <= cached_photo.attributes["DateTimeFile"]:
 | 
			
		||||
						cache_hit = True
 | 
			
		||||
						photo = cached_photo
 | 
			
		||||
				if not cache_hit:
 | 
			
		||||
					photo = Photo(entry, self.cache_path)
 | 
			
		||||
				if photo.is_valid:
 | 
			
		||||
					self.all_photos.append(photo)
 | 
			
		||||
					album.add_photo(photo)
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user