Make thumbnails.
This commit is contained in:
		
							
								
								
									
										114
									
								
								PhotoAlbum.py
									
									
									
									
									
								
							
							
						
						
									
										114
									
								
								PhotoAlbum.py
									
									
									
									
									
								
							@ -6,6 +6,8 @@ from PIL.ExifTags import TAGS
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def set_cache_path_base(base):
 | 
					def set_cache_path_base(base):
 | 
				
			||||||
	trim_base.base = base
 | 
						trim_base.base = base
 | 
				
			||||||
 | 
					def untrim_base(path):
 | 
				
			||||||
 | 
						return os.path.join(trim_base.base, path)
 | 
				
			||||||
def trim_base(path):
 | 
					def trim_base(path):
 | 
				
			||||||
	if path.startswith(trim_base.base):
 | 
						if path.startswith(trim_base.base):
 | 
				
			||||||
		path = path[len(trim_base.base):]
 | 
							path = path[len(trim_base.base):]
 | 
				
			||||||
@ -41,7 +43,7 @@ class Album(object):
 | 
				
			|||||||
	def date(self):
 | 
						def date(self):
 | 
				
			||||||
		self._sort()
 | 
							self._sort()
 | 
				
			||||||
		if len(self._photos) == 0 and len(self._albums) == 0:
 | 
							if len(self._photos) == 0 and len(self._albums) == 0:
 | 
				
			||||||
			return datetime.min
 | 
								return datetime(1900, 1, 1)
 | 
				
			||||||
		elif len(self._photos) == 0:
 | 
							elif len(self._photos) == 0:
 | 
				
			||||||
			return self._albums[-1].date
 | 
								return self._albums[-1].date
 | 
				
			||||||
		elif len(self._albums) == 0:
 | 
							elif len(self._albums) == 0:
 | 
				
			||||||
@ -77,7 +79,7 @@ class Album(object):
 | 
				
			|||||||
	def from_dict(dictionary, cripple=True):
 | 
						def from_dict(dictionary, cripple=True):
 | 
				
			||||||
		album = Album(dictionary["path"])
 | 
							album = Album(dictionary["path"])
 | 
				
			||||||
		for photo in dictionary["photos"]:
 | 
							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:
 | 
							if not cripple:
 | 
				
			||||||
			for subalbum in dictionary["albums"]:
 | 
								for subalbum in dictionary["albums"]:
 | 
				
			||||||
				album.add_album(Album.from_dict(subalbum), cripple)
 | 
									album.add_album(Album.from_dict(subalbum), cripple)
 | 
				
			||||||
@ -90,46 +92,104 @@ class Album(object):
 | 
				
			|||||||
		else:
 | 
							else:
 | 
				
			||||||
			subalbums = self._albums
 | 
								subalbums = self._albums
 | 
				
			||||||
		return { "path": self.path, "date": self.date, "albums": subalbums, "photos": self._photos }
 | 
							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):
 | 
					class Photo(object):
 | 
				
			||||||
	def __init__(self, path, 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))
 | 
							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
 | 
								self._attributes = attributes
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		self._attributes = {}
 | 
							self._attributes = {}
 | 
				
			||||||
		self._attributes["FileTime"] = mtime
 | 
							self._attributes["DateTimeFile"] = mtime
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		try:
 | 
							try:
 | 
				
			||||||
			i = Image.open(path)
 | 
								image = Image.open(path)
 | 
				
			||||||
		except:
 | 
							except:
 | 
				
			||||||
			self.is_valid = False
 | 
								self.is_valid = False
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
 | 
							self._metadata(image)
 | 
				
			||||||
 | 
							self._thumbnails(image, thumb_path)
 | 
				
			||||||
 | 
						def _metadata(self, image):
 | 
				
			||||||
		try:
 | 
							try:
 | 
				
			||||||
			info = i._getexif()
 | 
								info = image._getexif()
 | 
				
			||||||
		except:
 | 
							except:
 | 
				
			||||||
			info = None
 | 
								return
 | 
				
			||||||
		if info:
 | 
							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']:
 | 
									if isinstance(value, str):
 | 
				
			||||||
					if isinstance(value, str):
 | 
										value = value.strip()
 | 
				
			||||||
						value = value.strip()
 | 
										if decoded.startswith("DateTime"):
 | 
				
			||||||
						if decoded.startswith("DateTime"):
 | 
											try:
 | 
				
			||||||
							try:
 | 
												value = datetime.strptime(value, '%Y:%m:%d %H:%M:%S')
 | 
				
			||||||
								value = datetime.strptime(value, '%Y:%m:%d %H:%M:%S')
 | 
											except:
 | 
				
			||||||
							except:
 | 
												pass			
 | 
				
			||||||
								pass			
 | 
									self._attributes[decoded] = value
 | 
				
			||||||
					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
 | 
						@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
 | 
				
			||||||
	def cache_path(self, size):
 | 
					 | 
				
			||||||
		return image_cache(self.path, size)
 | 
					 | 
				
			||||||
	@property
 | 
						@property
 | 
				
			||||||
	def date(self):
 | 
						def date(self):
 | 
				
			||||||
		if "DateTimeOriginal" in self._attributes:
 | 
							if "DateTimeOriginal" in self._attributes:
 | 
				
			||||||
@ -137,7 +197,7 @@ class Photo(object):
 | 
				
			|||||||
		elif "DateTime" in self._attributes:
 | 
							elif "DateTime" in self._attributes:
 | 
				
			||||||
			return self._attributes["DateTime"]
 | 
								return self._attributes["DateTime"]
 | 
				
			||||||
		else:
 | 
							else:
 | 
				
			||||||
			return self._attributes["FileTime"]
 | 
								return self._attributes["DateTimeFile"]
 | 
				
			||||||
	def __cmp__(self, other):
 | 
						def __cmp__(self, other):
 | 
				
			||||||
		return cmp(self.date, other.date)
 | 
							return cmp(self.date, other.date)
 | 
				
			||||||
	@property
 | 
						@property
 | 
				
			||||||
@ -148,6 +208,12 @@ class Photo(object):
 | 
				
			|||||||
		del dictionary["date"]
 | 
							del dictionary["date"]
 | 
				
			||||||
		path = os.path.join(basepath, dictionary["name"])
 | 
							path = os.path.join(basepath, dictionary["name"])
 | 
				
			||||||
		del 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)
 | 
							return Photo(path, dictionary)
 | 
				
			||||||
	def to_dict(self):
 | 
						def to_dict(self):
 | 
				
			||||||
		photo = { "name": self.name, "date": self.date }
 | 
							photo = { "name": self.name, "date": self.date }
 | 
				
			||||||
@ -157,7 +223,7 @@ class Photo(object):
 | 
				
			|||||||
class PhotoAlbumEncoder(json.JSONEncoder):
 | 
					class PhotoAlbumEncoder(json.JSONEncoder):
 | 
				
			||||||
	def default(self, obj):
 | 
						def default(self, obj):
 | 
				
			||||||
		if isinstance(obj, datetime):
 | 
							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):
 | 
							if isinstance(obj, Album) or isinstance(obj, Photo):
 | 
				
			||||||
			return obj.to_dict()
 | 
								return obj.to_dict()
 | 
				
			||||||
		return json.JSONEncoder.default(self, obj)
 | 
							return json.JSONEncoder.default(self, obj)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import os.path
 | 
					import os.path
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
from PhotoAlbum import Photo, Album, json_cache, set_cache_path_base
 | 
					from PhotoAlbum import Photo, Album, json_cache, set_cache_path_base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TreeWalker:
 | 
					class TreeWalker:
 | 
				
			||||||
@ -14,17 +15,27 @@ class TreeWalker:
 | 
				
			|||||||
	def walk(self, path):
 | 
						def walk(self, path):
 | 
				
			||||||
		cache = os.path.join(self.cache_path, json_cache(path))
 | 
							cache = os.path.join(self.cache_path, json_cache(path))
 | 
				
			||||||
		cached = False
 | 
							cached = False
 | 
				
			||||||
		if os.path.exists(cache) and os.path.getmtime(path) <= os.path.getmtime(cache):
 | 
							cached_album = None
 | 
				
			||||||
			album = Album.from_cache(cache)
 | 
							if os.path.exists(cache):
 | 
				
			||||||
			cached = True
 | 
								cached_album = Album.from_cache(cache)
 | 
				
			||||||
		else:
 | 
								if os.path.getmtime(path) <= os.path.getmtime(cache):
 | 
				
			||||||
 | 
									cached = True
 | 
				
			||||||
 | 
									album = cached_album
 | 
				
			||||||
 | 
							if not cached:
 | 
				
			||||||
			album = Album(path)
 | 
								album = Album(path)
 | 
				
			||||||
		for entry in os.listdir(path):
 | 
							for entry in os.listdir(path):
 | 
				
			||||||
			entry = os.path.join(path, entry)
 | 
								entry = os.path.join(path, entry)
 | 
				
			||||||
			if os.path.isdir(entry):
 | 
								if os.path.isdir(entry):
 | 
				
			||||||
				album.add_album(self.walk(entry))
 | 
									album.add_album(self.walk(entry))
 | 
				
			||||||
			elif not cached and os.path.isfile(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:
 | 
									if photo.is_valid:
 | 
				
			||||||
					self.all_photos.append(photo)
 | 
										self.all_photos.append(photo)
 | 
				
			||||||
					album.add_photo(photo)
 | 
										album.add_photo(photo)
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user