mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-10-30 02:40:08 +00:00 
			
		
		
		
	
		
			
	
	
		
			319 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			319 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|  | #======================================================================= | ||
|  | 
 | ||
|  | __version__ = '''0.0.01''' | ||
|  | __sub_version__ = '''20130521225013''' | ||
|  | __copyright__ = '''(c) Alex A. Naanou 2012''' | ||
|  | 
 | ||
|  | 
 | ||
|  | #----------------------------------------------------------------------- | ||
|  | 
 | ||
|  | import os | ||
|  | import Image | ||
|  | import json | ||
|  | import sha | ||
|  | import urllib2 | ||
|  | 
 | ||
|  | from pli.logictypes import OR | ||
|  | 
 | ||
|  | import gid | ||
|  | 
 | ||
|  | 
 | ||
|  | #----------------------------------------------------------------------- | ||
|  | # XXX fanatically cleanup and normalise paths... | ||
|  | # XXX use real uuid's... | ||
|  | # | ||
|  | # TODO: | ||
|  | # 	- load config from file... | ||
|  | # 	- accept a path on command-line | ||
|  | # 	- default path is cwd | ||
|  | # 	- support nested fav's for ribbons | ||
|  | # | ||
|  | # Long Term TODO: | ||
|  | # 	- support processed images | ||
|  | # | ||
|  | # | ||
|  | #----------------------------------------------------------------------- | ||
|  | 
 | ||
|  | config = { | ||
|  | 	'format-version': '2.0', | ||
|  | 
 | ||
|  | 	'cache-structure': { | ||
|  | 		# XXX make these as close to standard as possible and keep | ||
|  | 		# 	  sane distances... | ||
|  | 		'150px': '.ImageGridCache/150px/', | ||
|  | 		'350px': '.ImageGridCache/350px/', | ||
|  | 		'900px': '.ImageGridCache/900px/', | ||
|  | 		'1080px': '.ImageGridCache/1080px/', | ||
|  | 		'1920px': '.ImageGridCache/1920px/', | ||
|  | 	}, | ||
|  | 
 | ||
|  | 	# gen1 format... | ||
|  | 	'json': '.ImageGridCache/all.json', | ||
|  | 
 | ||
|  | 	# gen3 format... | ||
|  | 	'images': '.ImageGridCache/images.json', | ||
|  | 	'data': '.ImageGridCache/data.json', | ||
|  | 	'marked': '.ImageGridCache/marked.json', | ||
|  | 
 | ||
|  | 	'error': '.ImageGridCache/error.log', | ||
|  | 	'sizes': { | ||
|  | 		'150px': 150, | ||
|  | 		'350px': 350, | ||
|  | 		'900px': 900, | ||
|  | 		'1080px': 1080, | ||
|  | 		'1920px': 1920, | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | images = { | ||
|  | 	'position': 0, | ||
|  | 	'ribbons':[ | ||
|  | 		{} | ||
|  | 	] | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | IMAGE_EXT = OR(*( | ||
|  | 		'.jpg', '.jpeg', '.JPG', '.JPEG', | ||
|  | )) | ||
|  | 
 | ||
|  | ERR_LOG = '''\
 | ||
|  | ERROR: %(error)s | ||
|  | SOURCE: %(source-file)s | ||
|  | TARGET: %(target-file)s | ||
|  | 
 | ||
|  | 
 | ||
|  | '''
 | ||
|  | 
 | ||
|  | CACHE_FILE_NAME = '%(guid)s - %(name)s' | ||
|  | 
 | ||
|  | 
 | ||
|  | #----------------------------------------------------------------------- | ||
|  | 
 | ||
|  | def pathjoin(*p): | ||
|  | 	'''
 | ||
|  | 	'''
 | ||
|  | 	return ('/'.join(p)).replace('//', '/') | ||
|  | 
 | ||
|  | def log_err(path, e, source_file, target_file): | ||
|  | 	'''
 | ||
|  | 	'''
 | ||
|  | 	err_file = pathjoin(path, config['error']) | ||
|  | 	if not os.path.exists(err_file): | ||
|  | 		err = open(err_file, 'w') | ||
|  | 	else: | ||
|  | 		err = open(err_file, 'a') | ||
|  | 	with err: | ||
|  | 		err.write(ERR_LOG % { | ||
|  | 				'source-file': source_file, | ||
|  | 				'target-file': target_file, | ||
|  | 				'error': e, | ||
|  | 		}) | ||
|  | 
 | ||
|  | # this should: | ||
|  | # 	1) see if image is cached, if yes return the cached guid (if dates match)... | ||
|  | # 	2) read the image file and get its guid  | ||
|  | ##!!! | ||
|  | def get_image_guid(path, force=False): | ||
|  | 	'''
 | ||
|  | 	'''
 | ||
|  | 	im = Image.open(path) | ||
|  | 	return sha.sha(im.tostring()).hexdigest() | ||
|  | ##	return sha.sha(open(path, 'r').read()) | ||
|  | 
 | ||
|  | 
 | ||
|  | # return list of paths ending in a pattern... | ||
|  | def build_cache_dirs(path, config=config): | ||
|  | 	'''
 | ||
|  | 	'''
 | ||
|  | 	dirs = config['cache-structure'] | ||
|  | 	for _, k in dirs.items(): | ||
|  | 		p = pathjoin(path, k) | ||
|  | 		if not os.path.exists(p): | ||
|  | 			os.makedirs(p) | ||
|  | 
 | ||
|  | 
 | ||
|  | # image format: | ||
|  | # 	{ | ||
|  | # 		'id': <image-id>, | ||
|  | # 		'preview': { | ||
|  | # 			<resolution>: <cache-path>, | ||
|  | # 			... | ||
|  | # 		}, | ||
|  | # 		'path': <image-path> | ||
|  | # 	} | ||
|  | # | ||
|  | def build_index(path, images=None, count=None): | ||
|  | 	'''
 | ||
|  | 	'''
 | ||
|  | 	dirs = config['cache-structure'] | ||
|  | 	sizes = config['sizes']  | ||
|  | 	n = -1 | ||
|  | 	if images == None: | ||
|  | 		images = { | ||
|  | 			'position': 0, | ||
|  | 			'ribbons': [{}], | ||
|  | 		} | ||
|  | 
 | ||
|  | 	for name in os.listdir(path): | ||
|  | 		# skip non-images... | ||
|  | 		iid, ext = os.path.splitext(name) | ||
|  | 		if ext != IMAGE_EXT: | ||
|  | 			continue | ||
|  | 
 | ||
|  | 		##!!! this is here for debuging... | ||
|  | 		n += 1 | ||
|  | 		if count != None and n >= count: | ||
|  | 			break | ||
|  | 
 | ||
|  | 		i =  { | ||
|  | 			'id': iid, | ||
|  | 			'preview': {}, | ||
|  | 			##!!! absolute paths??? | ||
|  | 			'path': 'file:///' + urllib2.quote(pathjoin(path, name), safe='/:'), | ||
|  | 		} | ||
|  | 		img = Image.open(pathjoin(path, name), 'r') | ||
|  | 		try: | ||
|  | 			iid = sha.sha(img.tostring()).hexdigest() | ||
|  | 		except IOError, e: | ||
|  | 			print 'x', | ||
|  | 			log_err(path, e, name, '-') | ||
|  | 			continue | ||
|  | 
 | ||
|  | 		if iid in images['ribbons'][0]: | ||
|  | 			print '_', | ||
|  | 			continue | ||
|  | 		i['id'] = iid | ||
|  | 		images['ribbons'][0][iid] = i | ||
|  | 		print '.', | ||
|  | 
 | ||
|  | 	return images | ||
|  | 
 | ||
|  | 
 | ||
|  | # XXX this will not overwrite existing files... | ||
|  | # XXX make this destingwish absolute and relative paths... | ||
|  | def make_cache_images(path, images, config=config): | ||
|  | 	'''
 | ||
|  | 	'''
 | ||
|  | 	dirs = config['cache-structure'] | ||
|  | 	sizes = config['sizes']  | ||
|  | 	n = 0 | ||
|  | 
 | ||
|  | 	for name in os.listdir(path): | ||
|  | 		# skip non-images... | ||
|  | 		iid, ext = os.path.splitext(name) | ||
|  | 		source_path = pathjoin(path, name) | ||
|  | 		if ext != IMAGE_EXT: | ||
|  | 			continue | ||
|  | 		n += 1 | ||
|  | 		i =  { | ||
|  | 			'id': iid, | ||
|  | 			'preview': {}, | ||
|  | ##			'path': pathjoin(path, name), | ||
|  | 			##!!! absolute paths??? | ||
|  | 			'path': 'file:///' + urllib2.quote(pathjoin(path, name), safe='/:'), | ||
|  | 			'ctime': os.path.getctime(source_path), | ||
|  | 		} | ||
|  | 		img = Image.open(source_path, 'r') | ||
|  | 		try: | ||
|  | 			##!!! use a real gid -- gid.image_gid(path, ...) | ||
|  | 			iid = sha.sha(img.tostring()).hexdigest() | ||
|  | 		except IOError, e: | ||
|  | 			print 'x', | ||
|  | 			log_err(path, e, name, '-') | ||
|  | 			i['error'] = 'IOError: ' + str(e) | ||
|  | 			continue | ||
|  | 		finally: | ||
|  | 			# we need to know which images are dead... | ||
|  | 			images['ribbons'][0][iid] = i | ||
|  | 		i['id'] = iid | ||
|  | 		# add original image to struct... | ||
|  | ##		i['preview'][str(max(*img.size)) + 'px'] = pathjoin(path, name) | ||
|  | 		i['preview'][str(max(*img.size)) + 'px'] = 'file:///' + urllib2.quote(pathjoin(path, name), safe='/:') | ||
|  | 		# previews... | ||
|  | 		for k, spec in sizes.items(): | ||
|  | 			p = pathjoin(path, dirs[k], CACHE_FILE_NAME % {'guid': iid, 'name': name}) | ||
|  | 			# do not upscale images... | ||
|  | 			if max(*img.size) <= spec: | ||
|  | 				continue | ||
|  | 			# add image to index... | ||
|  | 			if not os.path.exists(p): | ||
|  | 				scale = spec/float(max(*img.size)) | ||
|  | 				preview = img.resize((int(img.size[0]*scale), int(img.size[1]*scale)), Image.ANTIALIAS) | ||
|  | 				preview.save(p) | ||
|  | 				##!!! metadata??? | ||
|  | 				##!!! | ||
|  | 				print '.', | ||
|  | 			else: | ||
|  | 				# indicate an image skip... | ||
|  | 				print '_', | ||
|  | ##			i['preview'][str(spec) + 'px'] = p | ||
|  | 			##!!! absolute paths??? | ||
|  | 			i['preview'][str(spec) + 'px'] = 'file:///' + urllib2.quote(p, safe='/:') | ||
|  | 		# put original image in cache... | ||
|  | ##		i['preview'][str(spec) + 'px'] = p | ||
|  | 		i['preview'][str(spec) + 'px'] = 'file:///' + urllib2.quote(p, safe='/:') | ||
|  | 	images['position'] = images['ribbons'][0].keys()[0] | ||
|  | 	with open(pathjoin(path, config['json']), 'w') as f: | ||
|  | 		json.dump(images, f, indent=4) | ||
|  | 	##!!! STUB... | ||
|  | 	return n | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #----------------------------------------------------------------------- | ||
|  | 
 | ||
|  | def build_local_cache(path): | ||
|  | 	'''
 | ||
|  | 	'''
 | ||
|  | 	import time | ||
|  | 
 | ||
|  | 	t0 = time.time() | ||
|  | 
 | ||
|  | 	build_cache_dirs(path) | ||
|  | 
 | ||
|  | 	n = make_cache_images(path, images) | ||
|  | 
 | ||
|  | 	t1 = time.time() | ||
|  | 
 | ||
|  | 	print | ||
|  | 	print 'Processed %s images in %s seconds.' % (n, t1-t0) | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #----------------------------------------------------------------------- | ||
|  | if __name__ == '__main__': | ||
|  | 	from optparse import OptionParser | ||
|  | 
 | ||
|  | 	parser = OptionParser() | ||
|  | 
 | ||
|  | 	##!!! need to define the path so that it shoes up in -h | ||
|  | 
 | ||
|  | 	options, args = parser.parse_args() | ||
|  | 
 | ||
|  | 	if len(args) != 1: | ||
|  | 		parser.print_usage() | ||
|  | 	else: | ||
|  | 		IN_PATH = args[0] | ||
|  | 		IN_PATH = IN_PATH.replace('\\', '/') | ||
|  | 		build_local_cache(IN_PATH) | ||
|  | 
 | ||
|  | 
 | ||
|  | ##	PATH = 'images/cache-test/' | ||
|  | ##	PATH = 'L:/incoming/UNSORTED/Images/fav' | ||
|  | 
 | ||
|  | ##	build_local_cache(PATH) | ||
|  | 
 | ||
|  | ##	index = build_index(PATH, count=10) | ||
|  | ## | ||
|  | ##	import IPython | ||
|  | ##	IPython.embed() | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #======================================================================= | ||
|  | #                                            vim:set ts=4 sw=4 nowrap : |