mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-11-03 04:40:10 +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 :
							 |