aboutsummaryrefslogtreecommitdiffstats
path: root/PhotoAlbum.py
blob: 3e52086283dab83ce08b315dbbd77c0d26f9201f (plain) (blame)
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
from datetime import datetime
import json
import os.path
from PIL import Image
from PIL.ExifTags import TAGS

def set_cache_path_base(base):
	trim_base.base = base
def trim_base(path):
	if path.startswith(trim_base.base):
		path = path[len(trim_base.base):]
	if path.startswith('/'):
		path = path[1:]
	return path
def untrim_base(path):
	return os.path.join(trim_base.base, path)
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):
	def __init__(self, path):
		self._path = trim_base(path)
		self._photos = list()
		self._albums = list()
		self._photos_sorted = True
		self._albums_sorted = True
	@property
	def path(self):
		return self._path
	def __str__(self):
		return self.path
	@property
	def cache_path(self):
		return json_cache(self.path)
	@property
	def date(self):
		self._sort()
		if len(self._photos) == 0 and len(self._albums) == 0:
			return datetime.min
		elif len(self._photos) == 0:
			return self._albums[-1].date
		elif len(self._albums) == 0:
			return self._photos[-1].date
		return max(self._photos[-1].date, self._albums[-1].date)
	def __cmp__(self, other):
		return cmp(self.date, other.date)
	def add_photo(self, photo):
		self._photos.append(photo)
		self._photos_sorted = False
	def add_album(self, album):
		self._albums.append(album)
		self._photos_sorted = False
	def _sort(self):
		if not self._photos_sorted:
			self._photos.sort()
			self._photos_sorted = True
		if not self._albums_sorted:
			self._albums.sort()
			self._albums_sorted = True
	def cache(self, base_dir):
		self._sort()
		fp = open(os.path.join(base_dir, self.cache_path), 'w')
		json.dump(self, fp, cls=PhotoAlbumEncoder)
		fp.close()
	@staticmethod
	def from_cache(path):
		fp = open(path, "r")
		dictionary = json.load(fp)
		fp.close()
		return Album.from_dict(dictionary)
	@staticmethod
	def from_dict(dictionary, cripple=True):
		album = Album(dictionary["path"])
		for photo in dictionary["photos"]:
			album.add_photo(Photo.from_dict(photo, album.path))
		if not cripple:
			for subalbum in dictionary["albums"]:
				album.add_album(Album.from_dict(subalbum), cripple)
		album._sort()
		return album
	def to_dict(self, cripple=True):
		self._sort()
		if cripple:
			subalbums = [ { "path": sub.path, "date": sub.date } for sub in self._albums ]
		else:
			subalbums = self._albums
		return { "path": self.path, "date": self.date, "albums": subalbums, "photos": self._photos }
	
class Photo(object):
	def __init__(self, path, attributes=None):
		self._path = trim_base(path)
		self.is_valid = True
		if attributes is not None:
			self._attributes = attributes
			return
		else:
			self._attributes = {}
		try:
			i = Image.open(path)
		except:
			self.is_valid = False
			return
		try:
			info = i._getexif()
		except:
			info = None
		if info:
			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']:
					if isinstance(value, str):
						value = value.strip()
					self._attributes[decoded] = value
	@property
	def name(self):
		return os.path.basename(self._path)
	def __str__(self):
		return self.name
	@property
	def cache_paths(self):
		return [image_cache(self.path, size) for size in [100, 640, 1024]]
	@property
	def date(self):
		if "DateTime" in self._attributes:
			return datetime.strptime(self._attributes["DateTime"], '%Y:%m:%d %H:%M:%S')
		else:
			return datetime.fromtimestamp(os.path.getmtime(untrim_base(self._path)))
	def __cmp__(self, other):
		return cmp(self.date, other.date)
	@property
	def attributes(self):
		return self._attributes
	@staticmethod
	def from_dict(dictionary, basepath):
		del dictionary["date"]
		path = os.path.join(basepath, dictionary["name"])
		del dictionary["name"]
		return Photo(path, dictionary)
	def to_dict(self):
		photo = { "name": self.name, "date": self.date }
		photo.update(self.attributes)
		return photo

class PhotoAlbumEncoder(json.JSONEncoder):
	def default(self, obj):
		if isinstance(obj, datetime):
			return obj.isoformat()
		if isinstance(obj, Album) or isinstance(obj, Photo):
			return obj.to_dict()
		return json.JSONEncoder.default(self, obj)