ImageGrid/buildcache2.py

358 lines
8.3 KiB
Python
Executable File

#=======================================================================
__version__ = '''0.0.01'''
__sub_version__ = '''20130522020247'''
__copyright__ = '''(c) Alex A. Naanou 2011'''
#-----------------------------------------------------------------------
import os
import Image
import json
import sha
import urllib2
import time
from pli.logictypes import OR
import gid
#-----------------------------------------------------------------------
CONFIG = {
'absolute-path': False,
'cache-image-name': '%(guid)s - %(name)s',
'images': '.ImageGridCache/images.json',
'data': '.ImageGridCache/data.json',
'marked': '.ImageGridCache/marked.json',
'error': '.ImageGridCache/error.log',
'cache-structure': {
# 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/',
},
'sizes': {
'150px': 150,
'350px': 350,
'900px': 900,
'1080px': 1080,
'1920px': 1920,
}
}
DATA = {
'version': '2.0',
'current': None,
'ribbons': (),
'order': (),
'image_file': None,
}
IMAGE_EXT = OR(*(
'.jpg', '.jpeg', '.JPG', '.JPEG',
))
ERR_LOG = '''\
ERROR: %(error)s
SOURCE: %(source-file)s
TARGET: %(target-file)s
'''
#-----------------------------------------------------------------------
# Helpers...
#------------------------------------------------------------pathjoin---
def pathjoin(*p):
'''
'''
return ('/'.join(p)).replace('//', '/')
#-------------------------------------------------------------getpath---
def getpath(root, path, absolute=False):
'''
'''
if absolute == True:
return 'file:///' + urllib2.quote(pathjoin(root, path), safe='/:')
else:
return urllib2.quote(pathjoin(path), safe='/:')
#-------------------------------------------------------------log_err---
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,
})
#------------------------------------------------------------hash_gid---
def hash_gid(img, force=False):
'''
Generate gid based on preview file content.
NOTE: img can be either a path or an Image.
'''
if type(img) in (str, unicode):
img = Image.open(img)
return sha.sha(img.tostring()).hexdigest()
#-----------------------------------------------------report_progress---
def report_progress(img, status):
'''
'''
# created all previews...
if False not in status:
print '.',
# created no previews...
elif True not in status:
print '-',
# created some previews...
else:
print 'p',
return img
#-----------------------------------------make_inline_report_progress---
def make_inline_report_progress(state=None):
if state == None:
state = {}
if 'started at' not in state:
state['started at'] = time.time()
def _inline_report_progress(img, status):
created = state.get('created', 0)
skipped = state.get('skipped', 0)
partial = state.get('partial', 0)
# created all previews...
if False not in status:
created += 1
state['created'] = created
# created no previews...
elif True not in status:
skipped += 1
state['skipped'] = skipped
# created some previews...
else:
partial += 1
state['partial'] = partial
state['done at'] = time.time()
print 'Previews created: %s partial: %s skipped: %s...\r' % (created, partial, skipped),
return img
return _inline_report_progress
#-----------------------------------------------------------------------
# API...
#----------------------------------------------------build_cache_dirs---
def build_cache_dirs(path, config=CONFIG):
'''
Build cache directory tree.
'''
dirs = config['cache-structure']
for _, k in dirs.items():
p = pathjoin(path, k)
if not os.path.exists(p):
os.makedirs(p)
#--------------------------------------------------------build_images---
def build_images(path, config=CONFIG, gid_generator=hash_gid):
'''
Build image structures update images.json in cache.
'''
absolute_path = config['absolute-path']
for name in os.listdir(path):
fname, ext = os.path.splitext(name)
if ext != IMAGE_EXT:
continue
source_path = pathjoin(path, name)
img = {
'id': gid_generator(source_path),
'name': name,
'type': 'image',
'state': 'single',
'path': getpath(path, name, absolute_path),
'ctime': os.path.getctime(source_path),
'preview': {},
}
yield img
#------------------------------------------------------build_previews---
# NOTE: this will create images in the file system.
def build_previews(image, path=None, config=CONFIG, dry_run=True):
'''
NOTE: this needs the cache directory structure present.
'''
status = []
# config...
absolute_path = config['absolute-path']
dirs = config['cache-structure']
sizes = config['sizes']
cache_name = config['cache-image-name']
# data...
gid = image['id']
img_name = image['name']
name = os.path.splitext(img_name)[0]
img_path = image['path']
if absolute_path == False:
source_path = os.path.join(path, img_path)
else:
# XXX is this the best way???
o = urllib2.urlopen(img_path)
source_path = o.fp.name
o.close()
img = Image.open(source_path, 'r')
# biggest preview is the original image...
image['preview'][str(max(*img.size)) + 'px'] = img_path
# previews...
for k, spec in sizes.items():
if k in image['preview'].keys():
continue
# build the two paths: relative and full...
n = pathjoin(dirs[k], cache_name % {'guid': gid, 'name': img_name})
p = pathjoin(path, n)
# 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)
if not dry_run:
preview.save(p)
else:
preview.close()
##!!! metadata???
status += [True]
# image exists...
else:
status += [False]
image['preview'][str(spec) + 'px'] = getpath(path, n, absolute_path)
return image, status
#----------------------------------------------------------build_data---
def build_data(images, path, config=CONFIG):
'''
'''
images_index = {}
marked = []
data = DATA.copy()
ribbon = []
for image in images:
gid = image['id']
images_index[gid] = image
ribbon += [gid]
ribbon.sort(lambda a, b: cmp(images_index[b]['ctime'], images_index[a]['ctime']))
data['ribbons'] = [ribbon]
data['order'] = ribbon[:]
data['current'] = ribbon[0]
return data, images_index, marked
#-----------------------------------------------------------------------
# High-level API...
#---------------------------------------------------------build_cache---
##!!! DO NOT OVERWRITE EXISTING DATA...
def build_cache(path, config=CONFIG, gid_generator=hash_gid,
report_progress=report_progress, dry_run=False):
'''
'''
absolute_path = config['absolute-path']
build_cache_dirs(path, config)
data, images, marked = build_data(
(report_progress(*build_previews(img, path, config, dry_run=dry_run))
for img in build_images(path, config, gid_generator)),
path, config)
images_file = config['images']
data_file = config['data']
marked_file = config['marked']
data['image_file'] = getpath(path, images_file, absolute_path)
if not dry_run:
##!!! DO NOT OVERWRITE EXISTING DATA...
with open(os.path.join(path, images_file), 'w') as f:
json.dump(images, f, indent=4)
with open(os.path.join(path, data_file), 'w') as f:
json.dump(data, f, indent=4)
with open(os.path.join(path, marked_file), 'w') as f:
json.dump(marked, f, indent=4)
return data
#-----------------------------------------------------------------------
if __name__ == '__main__':
pass
#=======================================================================
# vim:set ts=4 sw=4 nowrap :