Better output on scanner.
This commit is contained in:
		| @ -1,6 +1,17 @@ | |||||||
| import os.path | import os.path | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
|  |  | ||||||
|  | def message(category, text): | ||||||
|  | 	if message.level <= 0: | ||||||
|  | 		sep = "  " | ||||||
|  | 	else: | ||||||
|  | 		sep = "--" | ||||||
|  | 	print "%s %s%s[%s]%s%s" % (datetime.now().isoformat(), max(0, message.level) * "  |", sep, category, max(1, (14 - len(category))) * " ", text) | ||||||
|  | message.level = -1 | ||||||
|  | def next_level(): | ||||||
|  | 	message.level += 1 | ||||||
|  | def back_level(): | ||||||
|  | 	message.level -= 1 | ||||||
| def set_cache_path_base(base): | def set_cache_path_base(base): | ||||||
| 	trim_base.base = base | 	trim_base.base = base | ||||||
| def untrim_base(path): | def untrim_base(path): | ||||||
|  | |||||||
| @ -110,6 +110,8 @@ class Photo(object): | |||||||
| 		self.is_valid = True | 		self.is_valid = True | ||||||
| 		try: | 		try: | ||||||
| 			mtime = file_mtime(path) | 			mtime = file_mtime(path) | ||||||
|  | 		except KeyboardInterrupt: | ||||||
|  | 			raise | ||||||
| 		except: | 		except: | ||||||
| 			self.is_valid = False | 			self.is_valid = False | ||||||
| 			return | 			return | ||||||
| @ -121,16 +123,20 @@ class Photo(object): | |||||||
| 		 | 		 | ||||||
| 		try: | 		try: | ||||||
| 			image = Image.open(path) | 			image = Image.open(path) | ||||||
|  | 		except KeyboardInterrupt: | ||||||
|  | 			raise | ||||||
| 		except: | 		except: | ||||||
| 			self.is_valid = False | 			self.is_valid = False | ||||||
| 			return | 			return | ||||||
| 		self._metadata(image) | 		self._metadata(image) | ||||||
| 		self._thumbnails(image, thumb_path) | 		self._thumbnails(image, thumb_path, path) | ||||||
| 	def _metadata(self, image): | 	def _metadata(self, image): | ||||||
| 		self._attributes["size"] = image.size | 		self._attributes["size"] = image.size | ||||||
| 		self._orientation = 1 | 		self._orientation = 1 | ||||||
| 		try: | 		try: | ||||||
| 			info = image._getexif() | 			info = image._getexif() | ||||||
|  | 		except KeyboardInterrupt: | ||||||
|  | 			raise | ||||||
| 		except: | 		except: | ||||||
| 			return | 			return | ||||||
| 		if not info: | 		if not info: | ||||||
| @ -144,6 +150,8 @@ class Photo(object): | |||||||
| 				if isinstance(decoded, str) and decoded.startswith("DateTime"): | 				if isinstance(decoded, str) and 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 KeyboardInterrupt: | ||||||
|  | 						raise | ||||||
| 					except: | 					except: | ||||||
| 						continue | 						continue | ||||||
| 			exif[decoded] = value | 			exif[decoded] = value | ||||||
| @ -175,6 +183,8 @@ class Photo(object): | |||||||
| 		if "Flash" in exif and exif["Flash"] in self._metadata.flash_dictionary: | 		if "Flash" in exif and exif["Flash"] in self._metadata.flash_dictionary: | ||||||
| 			try: | 			try: | ||||||
| 				self._attributes["flash"] = self._metadata.flash_dictionary[exif["Flash"]] | 				self._attributes["flash"] = self._metadata.flash_dictionary[exif["Flash"]] | ||||||
|  | 			except KeyboardInterrupt: | ||||||
|  | 				raise | ||||||
| 			except: | 			except: | ||||||
| 				pass | 				pass | ||||||
| 		if "ExposureProgram" in exif and exif["ExposureProgram"] < len(self._metadata.exposure_list): | 		if "ExposureProgram" in exif and exif["ExposureProgram"] < len(self._metadata.exposure_list): | ||||||
| @ -197,19 +207,26 @@ class Photo(object): | |||||||
| 	_metadata.exposure_list = ["Not Defined", "Manual", "Program AE", "Aperture-priority AE", "Shutter speed priority AE", "Creative (Slow speed)", "Action (High speed)", "Portrait", "Landscape", "Bulb"] | 	_metadata.exposure_list = ["Not Defined", "Manual", "Program AE", "Aperture-priority AE", "Shutter speed priority AE", "Creative (Slow speed)", "Action (High speed)", "Portrait", "Landscape", "Bulb"] | ||||||
| 	_metadata.orientation_list = ["Horizontal (normal)", "Mirror horizontal", "Rotate 180", "Mirror vertical", "Mirror horizontal and rotate 270 CW", "Rotate 90 CW", "Mirror horizontal and rotate 90 CW", "Rotate 270 CW"] | 	_metadata.orientation_list = ["Horizontal (normal)", "Mirror horizontal", "Rotate 180", "Mirror vertical", "Mirror horizontal and rotate 270 CW", "Rotate 90 CW", "Mirror horizontal and rotate 90 CW", "Rotate 270 CW"] | ||||||
| 		 | 		 | ||||||
| 	def _thumbnail(self, image, thumb_path, size, square=False): | 	def _thumbnail(self, image, thumb_path, original_path, size, square=False): | ||||||
| 		thumb_path = os.path.join(thumb_path, image_cache(self._path, size, square)) | 		thumb_path = os.path.join(thumb_path, image_cache(self._path, size, square)) | ||||||
| 		print "Thumbing %s" % thumb_path | 		info_string = "%s -> %spx" % (os.path.basename(original_path), str(size)) | ||||||
|  | 		if square: | ||||||
|  | 			info_string += ", square" | ||||||
|  | 		message("thumbing", info_string) | ||||||
| 		if os.path.exists(thumb_path) and file_mtime(thumb_path) >= self._attributes["dateTimeFile"]: | 		if os.path.exists(thumb_path) and file_mtime(thumb_path) >= self._attributes["dateTimeFile"]: | ||||||
| 			return | 			return | ||||||
| 		gc.collect() | 		gc.collect() | ||||||
| 		try: | 		try: | ||||||
| 			image = image.copy() | 			image = image.copy() | ||||||
|  | 		except KeyboardInterrupt: | ||||||
|  | 			raise | ||||||
| 		except: | 		except: | ||||||
| 			try: | 			try: | ||||||
| 				image = image.copy() # we try again to work around PIL bug | 				image = image.copy() # we try again to work around PIL bug | ||||||
|  | 			except KeyboardInterrupt: | ||||||
|  | 				raise | ||||||
| 			except: | 			except: | ||||||
| 				print "Image is corrupted. %s will not be created." % thumb_path | 				message("corrupt image", os.path.basename(original_path)) | ||||||
| 				return | 				return | ||||||
| 		if square: | 		if square: | ||||||
| 			if image.size[0] > image.size[1]: | 			if image.size[0] > image.size[1]: | ||||||
| @ -227,11 +244,14 @@ class Photo(object): | |||||||
| 		image.thumbnail((size, size), Image.ANTIALIAS) | 		image.thumbnail((size, size), Image.ANTIALIAS) | ||||||
| 		try: | 		try: | ||||||
| 			image.save(thumb_path, "JPEG") | 			image.save(thumb_path, "JPEG") | ||||||
|  | 		except KeyboardInterrupt: | ||||||
|  | 			os.unlink(thumb_path) | ||||||
|  | 			raise | ||||||
| 		except: | 		except: | ||||||
| 			print "Could not thumbnail %s" % thumb_path | 			message("save failure", os.path.basename(thumb_path)) | ||||||
| 			os.unlink(thumb_path) | 			os.unlink(thumb_path) | ||||||
| 		 | 		 | ||||||
| 	def _thumbnails(self, image, thumb_path): | 	def _thumbnails(self, image, thumb_path, original_path): | ||||||
| 		mirror = image | 		mirror = image | ||||||
| 		if self._orientation == 2: | 		if self._orientation == 2: | ||||||
| 			# Vertical Mirror | 			# Vertical Mirror | ||||||
| @ -255,7 +275,7 @@ class Photo(object): | |||||||
| 			# Rotation 90 | 			# Rotation 90 | ||||||
| 			mirror = image.transpose(Image.ROTATE_90) | 			mirror = image.transpose(Image.ROTATE_90) | ||||||
| 		for size in Photo.thumb_sizes: | 		for size in Photo.thumb_sizes: | ||||||
| 			self._thumbnail(mirror, thumb_path, size[0], size[1]) | 			self._thumbnail(mirror, thumb_path, original_path, size[0], size[1]) | ||||||
| 	@property | 	@property | ||||||
| 	def name(self): | 	def name(self): | ||||||
| 		return os.path.basename(self._path) | 		return os.path.basename(self._path) | ||||||
| @ -294,6 +314,8 @@ class Photo(object): | |||||||
| 			if key.startswith("dateTime"): | 			if key.startswith("dateTime"): | ||||||
| 				try: | 				try: | ||||||
| 					dictionary[key] = datetime.strptime(dictionary[key], "%a %b %d %H:%M:%S %Y") | 					dictionary[key] = datetime.strptime(dictionary[key], "%a %b %d %H:%M:%S %Y") | ||||||
|  | 				except KeyboardInterrupt: | ||||||
|  | 					raise | ||||||
| 				except: | 				except: | ||||||
| 					pass | 					pass | ||||||
| 		return Photo(path, None, dictionary) | 		return Photo(path, None, dictionary) | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ import os.path | |||||||
| import sys | import sys | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from PhotoAlbum import Photo, Album, PhotoAlbumEncoder | from PhotoAlbum import Photo, Album, PhotoAlbumEncoder | ||||||
| from CachePath import json_cache, set_cache_path_base, file_mtime | from CachePath import * | ||||||
| import json | import json | ||||||
|  |  | ||||||
| class TreeWalker: | class TreeWalker: | ||||||
| @ -16,23 +16,28 @@ class TreeWalker: | |||||||
| 		self.walk(self.album_path) | 		self.walk(self.album_path) | ||||||
| 		self.big_lists() | 		self.big_lists() | ||||||
| 		self.remove_stale() | 		self.remove_stale() | ||||||
|  | 		message("complete", "") | ||||||
| 	def walk(self, path): | 	def walk(self, path): | ||||||
| 		print "Walking %s" % path | 		next_level() | ||||||
|  | 		message("walking", os.path.basename(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 | ||||||
| 		cached_album = None | 		cached_album = None | ||||||
| 		if os.path.exists(cache): | 		if os.path.exists(cache): | ||||||
| 			print "Has cache %s" % path |  | ||||||
| 			try: | 			try: | ||||||
| 				cached_album = Album.from_cache(cache) | 				cached_album = Album.from_cache(cache) | ||||||
| 				if file_mtime(path) <= file_mtime(cache): | 				if file_mtime(path) <= file_mtime(cache): | ||||||
| 					print "Album is fully cached" | 					message("full cache", os.path.basename(path)) | ||||||
| 					cached = True | 					cached = True | ||||||
| 					album = cached_album | 					album = cached_album | ||||||
| 					for photo in album.photos: | 					for photo in album.photos: | ||||||
| 						self.all_photos.append(photo) | 						self.all_photos.append(photo) | ||||||
|  | 				else: | ||||||
|  | 					message("partial cache", os.path.basename(path)) | ||||||
|  | 			except KeyboardInterrupt: | ||||||
|  | 				raise | ||||||
| 			except: | 			except: | ||||||
| 				print "Cache is corrupted. Rescanning album." | 				message("corrupt cache", os.path.basename(path)) | ||||||
| 				cached_album = None | 				cached_album = None | ||||||
| 		if not cached: | 		if not cached: | ||||||
| 			album = Album(path) | 			album = Album(path) | ||||||
| @ -41,61 +46,70 @@ class TreeWalker: | |||||||
| 				continue | 				continue | ||||||
| 			try: | 			try: | ||||||
| 				entry = entry.decode(sys.getfilesystemencoding()) | 				entry = entry.decode(sys.getfilesystemencoding()) | ||||||
|  | 			except KeyboardInterrupt: | ||||||
|  | 				raise | ||||||
| 			except: | 			except: | ||||||
| 				pass | 				pass | ||||||
| 			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): | ||||||
|  | 				next_level() | ||||||
| 				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 file_mtime(entry) <= cached_photo.attributes["dateTimeFile"]: | 					if cached_photo and file_mtime(entry) <= cached_photo.attributes["dateTimeFile"]: | ||||||
| 						print "Photo cache hit %s" % entry | 						message("cache hit", os.path.basename(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 | 					message("metainfo", os.path.basename(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) | ||||||
| 					album.add_photo(photo) | 					album.add_photo(photo) | ||||||
|  | 				else: | ||||||
|  | 					message("unreadable", os.path.basename(entry)) | ||||||
|  | 				back_level() | ||||||
| 		if not album.empty: | 		if not album.empty: | ||||||
| 			print "Writing cache of %s" % album.cache_path | 			message("caching", os.path.basename(path)) | ||||||
| 			album.cache(self.cache_path) | 			album.cache(self.cache_path) | ||||||
| 			self.all_albums.append(album) | 			self.all_albums.append(album) | ||||||
| 		else: | 		else: | ||||||
| 			print "Not writing cache of %s because it's empty" % album.cache_path | 			message("empty", os.path.basename(path)) | ||||||
|  | 		back_level() | ||||||
| 		return album | 		return album | ||||||
| 	def big_lists(self): | 	def big_lists(self): | ||||||
| 		photo_list = [] | 		photo_list = [] | ||||||
| 		self.all_photos.sort() | 		self.all_photos.sort() | ||||||
| 		for photo in self.all_photos: | 		for photo in self.all_photos: | ||||||
| 			photo_list.append(photo.path) | 			photo_list.append(photo.path) | ||||||
| 		print "Writing all photos list." | 		message("caching", "all photos path list") | ||||||
| 		fp = open(os.path.join(self.cache_path, "all_photos.json"), 'w') | 		fp = open(os.path.join(self.cache_path, "all_photos.json"), 'w') | ||||||
| 		json.dump(photo_list, fp, cls=PhotoAlbumEncoder) | 		json.dump(photo_list, fp, cls=PhotoAlbumEncoder) | ||||||
| 		fp.close() | 		fp.close() | ||||||
| 		photo_list.reverse() | 		photo_list.reverse() | ||||||
| 		print "Writing latest photos list." | 		message("caching", "latest photos path list") | ||||||
| 		fp = open(os.path.join(self.cache_path, "latest_photos.json"), 'w') | 		fp = open(os.path.join(self.cache_path, "latest_photos.json"), 'w') | ||||||
| 		json.dump(photo_list[0:27], fp, cls=PhotoAlbumEncoder) | 		json.dump(photo_list[0:27], fp, cls=PhotoAlbumEncoder) | ||||||
| 		fp.close() | 		fp.close() | ||||||
| 		 | 		 | ||||||
| 	def remove_stale(self): | 	def remove_stale(self): | ||||||
| 		print "Building list of all cache entries." | 		message("cleanup", "building stale list") | ||||||
| 		all_cache_entries = { "all_photos.json": True, "latest_photos.json": True } | 		all_cache_entries = { "all_photos.json": True, "latest_photos.json": True } | ||||||
| 		for album in self.all_albums: | 		for album in self.all_albums: | ||||||
| 			all_cache_entries[album.cache_path] = True | 			all_cache_entries[album.cache_path] = True | ||||||
| 		for photo in self.all_photos: | 		for photo in self.all_photos: | ||||||
| 			for entry in photo.image_caches: | 			for entry in photo.image_caches: | ||||||
| 				all_cache_entries[entry] = True | 				all_cache_entries[entry] = True | ||||||
| 		print "Searching stale cache entries." | 		message("cleanup", "searching for stale cache entries") | ||||||
| 		for cache in os.listdir(self.cache_path): | 		for cache in os.listdir(self.cache_path): | ||||||
| 			try: | 			try: | ||||||
| 				cache = cache.decode(sys.getfilesystemencoding()) | 				cache = cache.decode(sys.getfilesystemencoding()) | ||||||
|  | 			except KeyboardInterrupt: | ||||||
|  | 				raise | ||||||
| 			except: | 			except: | ||||||
| 				pass | 				pass | ||||||
| 			if cache not in all_cache_entries: | 			if cache not in all_cache_entries: | ||||||
| 				print "Removing stale cache %s" % cache | 				message("cleanup", os.path.basename(cache)) | ||||||
| 				os.unlink(os.path.join(self.cache_path, cache)) | 				os.unlink(os.path.join(self.cache_path, cache)) | ||||||
|  | |||||||
| @ -1,13 +1,18 @@ | |||||||
| #!/usr/bin/env python | #!/usr/bin/env python | ||||||
|  |  | ||||||
| from TreeWalker import TreeWalker | from TreeWalker import TreeWalker | ||||||
| from sys import argv | from sys import argv, exit | ||||||
|  | from CachePath import message | ||||||
|  |  | ||||||
| def main(): | def main(): | ||||||
| 	if len(argv) != 3: | 	if len(argv) != 3: | ||||||
| 		print "usage: %s ALBUM_PATH CACHE_PATH" % argv[0] | 		print "usage: %s ALBUM_PATH CACHE_PATH" % argv[0] | ||||||
| 		return | 		return | ||||||
|  | 	try: | ||||||
| 		TreeWalker(argv[1], argv[2]) | 		TreeWalker(argv[1], argv[2]) | ||||||
|  | 	except KeyboardInterrupt: | ||||||
|  | 		message("keyboard", "CTRL+C pressed, quitting.") | ||||||
|  | 		exit(-97) | ||||||
| 	 | 	 | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
| 	main() | 	main() | ||||||
		Reference in New Issue
	
	Block a user