From b50f399f3dfc148c65067915c8a77996a5fbf67e Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Tue, 28 May 2013 21:59:38 +0400 Subject: [PATCH] buildcache.py now supports RAW input images (slow), some refactoring on ui... Signed-off-by: Alex A. Naanou --- buildcache.py | 78 +++++++++++++++++++++++++----- ui/base.js | 100 +++++++++++++++++++++------------------ ui/data.js | 118 ++++++++++++++++++++++++---------------------- ui/keybindings.js | 6 +-- ui/marks.js | 8 ++-- 5 files changed, 189 insertions(+), 121 deletions(-) diff --git a/buildcache.py b/buildcache.py index a02d4770..a0e7ff99 100755 --- a/buildcache.py +++ b/buildcache.py @@ -1,7 +1,7 @@ #======================================================================= __version__ = '''0.0.01''' -__sub_version__ = '''20130528154633''' +__sub_version__ = '''20130528215723''' __copyright__ = '''(c) Alex A. Naanou 2011''' @@ -13,8 +13,14 @@ import json import sha import urllib2 import time +import tempfile from optparse import OptionParser, OptionGroup +try: + import pyexiv2 as metadata +except: + metadata = None + from pli.logictypes import OR import gid @@ -51,6 +57,7 @@ CONFIG = { '900px': '900px/', '1080px': '1080px/', '1920px': '1920px/', + 'preview': 'preview/', }, 'sizes': { '150px': 150, @@ -69,10 +76,6 @@ DATA = { 'image_file': None, } -IMAGE_EXT = OR(*( - '.jpg', '.jpeg', '.JPG', '.JPEG', -)) - ERR_LOG = '''\ ERROR: %(error)s SOURCE: %(source-file)s @@ -82,6 +85,25 @@ TARGET: %(target-file)s ''' +RAW = OR( + # Nikon + 'NEF', 'nef', + # Panasonic/Leica + 'RW2', 'rw2', + # Canon + 'CRW', 'crw', + 'CR2', 'cr2', + # Sigma + 'X3F', 'x3f', + # Adobe/Leica + 'DNG', 'dng', +) + +IMAGE = OR( + 'jpg', 'jpeg', 'JPG', 'JPEG', +) + + #----------------------------------------------------------------------- # Helpers... #------------------------------------------------------------pathjoin--- @@ -95,6 +117,10 @@ def pathjoin(*p): def getpath(root, path, absolute=False): ''' ''' + if root in path: + path = path.split(root)[-1] + if path[0] in ('\\', '/'): + path = path[1:] if absolute == True: return 'file:///' + urllib2.quote(pathjoin(root, path), safe='/:') else: @@ -217,24 +243,52 @@ def build_images(path, config=CONFIG, gid_generator=hash_gid, verbosity=0): for name in os.listdir(path): fname, ext = os.path.splitext(name) + ext = ext[1:] - if ext != IMAGE_EXT: + # extract raw preview... + # XXX this is really slow, need a better way to do this... + if ext == RAW and metadata != None: + source_path = pathjoin(path, name) + raw = metadata.ImageMetadata(source_path) + raw.read() + ##!!! can there be no previews? + # get the biggest preview... + preview = raw.previews[0] + for p in raw.previews: + if max(preview.dimensions) < max(p.dimensions): + preview = p + + source_path = pathjoin(path, CONFIG['cache-dir'], CONFIG['cache-structure']['preview'], fname + '.jpg') + + with open(source_path, 'w+b') as p: + p.write(preview.data) + + # copy metadata... + preview = metadata.ImageMetadata(source_path) + preview.read() + raw.copy(preview) + preview.write() + + # normal images... + elif ext == IMAGE: + source_path = pathjoin(path, name) + + # skip other files... + else: 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), + 'path': getpath(path, source_path, absolute_path), + 'ctime': os.path.getctime(pathjoin(path, name)), 'preview': {}, } if verbosity >= 2: - print (' '*72) + '\rProcessing image: %s' % getpath(path, name, absolute_path) + print (' '*72) + '\rProcessing image: %s' % getpath(path, source_path, absolute_path) yield img @@ -281,7 +335,7 @@ def build_previews(image, path=None, config=CONFIG, dry_run=True, verbosity=0): continue # build the two paths: relative and full... - n = pathjoin(cache_dir, dirs[k], cache_name % {'guid': gid, 'name': img_name}) + n = pathjoin(cache_dir, dirs[k], cache_name % {'guid': gid, 'name': name + '.jpg'}) p = pathjoin(path, n) # do not upscale images... diff --git a/ui/base.js b/ui/base.js index af3e4e04..99a2e8a1 100755 --- a/ui/base.js +++ b/ui/base.js @@ -103,23 +103,51 @@ function flashIndicator(direction){ } -function getRibbon(image){ - image = image == null ? $('.current.image') : $(image) - return image.closest('.ribbon') +function getImage(gid){ + var res + // current or first (no gid given) + if(gid == null){ + res = $('.current.image') + return res.length == 0 ? $('.image').first() : res + } + + // gid... + res = $('.image[gid='+ JSON.stringify(gid) +']') + if(res.length != null){ + return res + } + + // order... + res = $('.image[order='+ JSON.stringify(gid) +']') + if(res.length != null){ + return res + } + + return null } -function getImage(gid){ - if(e == null){ - return $('.current.image') +function getImageOrder(image){ + image = image == null ? getImage() : $(image) + if(image.length == 0){ + return } - // XXX do a proper check... - // gid... - return $('.image[gid='+ JSON.stringify(gid) +']') - - // order... - // XXX - //return $('.image[order='+ JSON.stringify(gid) +']') + return JSON.parse(image.attr('order')) +} + + +function getImageGID(image){ + image = image == null ? getImage() : $(image) + if(image.length == 0){ + return + } + return JSON.parse(image.attr('gid')) +} + + +function getRibbon(image){ + image = image == null ? getImage() : $(image) + return image.closest('.ribbon') } @@ -139,24 +167,6 @@ function getRibbonIndex(elem){ } -function getImageOrder(image){ - image = image == null ? $('.current.image') : $(image) - if(image.length == 0){ - return - } - return JSON.parse(image.attr('order')) -} - - -function getImageGID(image){ - image = image == null ? $('.current.image') : $(image) - if(image.length == 0){ - return - } - return JSON.parse(image.attr('gid')) -} - - // Calculate relative position between two elements // // NOTE: tried to make this as brain-dead-stupidly-simple as possible... @@ -213,7 +223,7 @@ function getScreenWidthInImages(size){ // getGIDBefore(...) that will check the full data... function getImageBefore(image, ribbon, mode){ mode = mode == null ? NAV_DEFAULT : mode - image = image == null ? $('.current.image') : $(image) + image = image == null ? getImage() : $(image) if(ribbon == null){ ribbon = getRibbon(image) } @@ -260,7 +270,7 @@ function shiftTo(image, ribbon){ function shiftImage(direction, image, force_create_ribbon){ - image = image == null ? $('.current.image') : $(image) + image = image == null ? getImage() : $(image) var old_ribbon = getRibbon(image) var ribbon = old_ribbon[direction]('.ribbon') @@ -534,7 +544,7 @@ function centerView(image, mode){ $('.viewer').trigger('preCenteringView', [getRibbon(image), image]) if(image == null || image.length == 0){ - image = $('.current.image') + image = getImage() } var viewer = $('.viewer') // XXX should these be "inner"??? @@ -583,7 +593,7 @@ function centerView(image, mode){ // center relative to target (given) via the ribbon left // only left coordinate is changed... // -// NOTE: image defaults to $('.current.image'). +// NOTE: image defaults to getImage(). // // XXX might be good to merge this and centerImage... // ...or make a generic centering function... @@ -593,7 +603,7 @@ function centerView(image, mode){ function centerRibbon(ribbon, image, mode){ mode = mode == null ? TRANSITION_MODE_DEFAULT : mode ribbon = ribbon == null ? getRibbon() : $(ribbon) - image = image == null ? $('.current.image') : $(image) + image = image == null ? getImage() : $(image) $('.viewer').trigger('preCenteringRibbon', [ribbon, image]) @@ -684,7 +694,7 @@ function dblClickHandler(evt){ function nextImage(n, mode){ mode = mode == null ? NAV_DEFAULT : mode n = n == null ? 1 : n - var target = $('.current.image').nextAll('.image' + mode) + var target = getImage().nextAll('.image' + mode) if(target.length < n){ target = target.last() // XXX this fires if we hit the end of the currently loaded @@ -699,7 +709,7 @@ function nextImage(n, mode){ function prevImage(n, mode){ mode = mode == null ? NAV_DEFAULT : mode n = n == null ? 1 : n - var target = $('.current.image').prevAll('.image' + mode) + var target = getImage().prevAll('.image' + mode) if(target.length < n){ target = target.last() // XXX this fires if we hit the end of the currently loaded @@ -726,7 +736,7 @@ function firstImage(mode){ $('.viewer').trigger('requestedFirstImage', [getRibbon()]) mode = mode == null ? NAV_DEFAULT : mode - if($('.current.image').prevAll('.image' + mode).length == 0){ + if(getImage().prevAll('.image' + mode).length == 0){ flashIndicator('start') } return centerView( @@ -738,7 +748,7 @@ function lastImage(mode){ $('.viewer').trigger('requestedLastImage', [getRibbon()]) mode = mode == null ? NAV_DEFAULT : mode - if($('.current.image').nextAll('.image' + mode).length == 0){ + if(getImage().nextAll('.image' + mode).length == 0){ flashIndicator('end') } return centerView( @@ -753,7 +763,7 @@ function lastImage(mode){ // on direction... function prevRibbon(mode){ mode = mode == null ? NAV_DEFAULT : mode - var cur = $('.current.image') + var cur = getImage() var target = getImageBefore(cur, getRibbon(cur).prevAll('.ribbon' + NAV_RIBBON_DEFAULT).first()) @@ -772,7 +782,7 @@ function prevRibbon(mode){ } function nextRibbon(mode){ mode = mode == null ? NAV_DEFAULT : mode - var cur = $('.current.image') + var cur = getImage() var target = getImageBefore(cur, getRibbon(cur).nextAll('.ribbon' + NAV_RIBBON_DEFAULT).first()) @@ -870,7 +880,7 @@ var _ccw = { function rotateImage(direction, image){ var r_table = direction == 'left' ? _cw : _ccw - image = image == null ? $('.current.image') : $(image) + image = image == null ? getImage() : $(image) image.each(function(i, e){ var img = $(this) var o = r_table[img.attr('orientation')] @@ -909,7 +919,7 @@ function flipHorizontal(image){ /********************************************************* Zooming ***/ function fitNImages(n){ - var image = $('.current.image') + var image = getImage() var w = image.outerWidth(true) var h = image.outerHeight(true) @@ -952,7 +962,7 @@ function zoomOut(){ function shiftImageTo(image, direction, moving, force_create_ribbon, mode){ if(image == null){ - image = $('.current.image') + image = getImage() } mode = mode == null ? NAV_DEFAULT : mode diff --git a/ui/data.js b/ui/data.js index 5aa2d988..74d093de 100755 --- a/ui/data.js +++ b/ui/data.js @@ -213,10 +213,66 @@ Array.prototype.binSearch = function(target, cmp){ } +// Normalize the path... +// +// This will: +// - convert windows absolute paths 'X:\...' -> 'file:///X:/...' +// - if mode is 'absolute': +// - return absolute paths as-is +// - base relative paths on base/BASE_URL, returning an absolute +// path +// - if mode is relative: +// - if absolute path is based on base/BASE_URL make a relative +// to base path out of it buy cutting the base out. +// - return absolute paths as-is +// - return relative paths as-is +// +// NOTE: mode can be either 'absolute' (default) or 'relative'... +function normalizePath(url, base, mode){ + mode = mode == null ? 'absolute' : mode + base = base == null ? BASE_URL : base + + // windows path... + // - replace all '\\' with '/'... + url = url.replace(/\\/g, '/') + // - replace 'X:/...' with 'file:///X:/...' + if(/^[A-Z]:\//.test(url)){ + url = 'file:///' + url + } + + // we got absolute path... + if(/^(file|http|https):\/\/.*$/.test(url)){ + // check if we start with base, and remove it if so... + if(mode == 'relative' && url.substring(0, base.length) == base){ + url = url.substring(base.length - 1) + return url[0] == '/' ? url.substring(1) : url + + // if it's a different path, return as-is + } else if(mode == 'absolute'){ + return url + } + + // make an absolute path... + } else if(mode == 'absolute') { + // if base ends and url starts with '.' avoid making it a '..' + if(base[base.length-1] == '.' && url[0] == '.'){ + return base + url.substring(1) + // avoid creating '//'... + } else if(base[base.length-1] != '/' && url[0] != '/'){ + return base + '/' + url + } else { + return base + url + } + } +} + + // Same as getImageBefore, but uses gids and searches in DATA... // // NOTE: this uses it's own predicate... function getGIDBefore(gid, ribbon, search){ + gid = gid == null ? getImageGID() : gid + ribbon = ribbon == null ? getRibbonIndex() : ribbon search = search == null ? binSearch : search //search = search == null ? match2(linSearch, binSearch) : search ribbon = DATA.ribbons[ribbon] @@ -293,6 +349,7 @@ function getImageGIDs(from, count, ribbon, inclusive){ // // NOTE: this will use the original if everything else is smaller... function getBestPreview(gid, size){ + gid = gid == null ? getImageGID(): gid size = size == null ? getVisibleImageSize('max') : size var s var img_data = IMAGES[gid] @@ -315,66 +372,12 @@ function getBestPreview(gid, size){ } -// Normalize the path... -// -// This will: -// - convert windows absolute paths 'X:\...' -> 'file:///X:/...' -// - if mode is 'absolute': -// - return absolute paths as-is -// - base relative paths on base/BASE_URL, returning an absolute -// path -// - if mode is relative: -// - if absolute path is based on base/BASE_URL make a relative -// to base path out of it buy cutting the base out. -// - return absolute paths as-is -// - return relative paths as-is -// -// NOTE: mode can be either 'absolute' (default) or 'relative'... -function normalizePath(url, base, mode){ - mode = mode == null ? 'absolute' : mode - base = base == null ? BASE_URL : base - - // windows path... - // - replace all '\\' with '/'... - url = url.replace(/\\/g, '/') - // - replace 'X:/...' with 'file:///X:/...' - if(/^[A-Z]:\//.test(url)){ - url = 'file:///' + url - } - - // we got absolute path... - if(/^(file|http|https):\/\/.*$/.test(url)){ - // check if we start with base, and remove it if so... - if(mode == 'relative' && url.substring(0, base.length) == base){ - url = url.substring(base.length - 1) - return url[0] == '/' ? url.substring(1) : url - - // if it's a different path, return as-is - } else if(mode == 'absolute'){ - return url - } - - // make an absolute path... - } else if(mode == 'absolute') { - // if base ends and url starts with '.' avoid making it a '..' - if(base[base.length-1] == '.' && url[0] == '.'){ - return base + url.substring(1) - // avoid creating '//'... - } else if(base[base.length-1] != '/' && url[0] != '/'){ - return base + '/' + url - } else { - return base + url - } - } -} - - /********************************************************************** * Constructors */ -function urlList2Images(lst){ +function imagesFromUrls(lst){ var res = {} $.each(lst, function(i, e){ @@ -509,7 +512,7 @@ function updateImage(image, gid, size){ } -// shorthand... +// Same as updateImage(...) but will update all images. function updateImages(size){ size = size == null ? getVisibleImageSize('max') : size return $('.image').each(function(){ @@ -841,6 +844,7 @@ function saveLocalStorage(attr){ * XXX need to cleanup this section... */ +// load the target-specific handlers... // CEF if(window.CEF_dumpJSON != null){ var dumpJSON = CEF_dumpJSON @@ -1091,7 +1095,7 @@ function loadDir(path, raw_load){ return } - IMAGES = urlList2Images(image_paths) + IMAGES = imagesFromUrls(image_paths) DATA = dataFromImages(IMAGES) MARKED = [] BASE_URL = orig_path diff --git a/ui/keybindings.js b/ui/keybindings.js index 52bbd488..8f656973 100755 --- a/ui/keybindings.js +++ b/ui/keybindings.js @@ -246,7 +246,7 @@ var KEYBOARD_CONFIG = { } else { prevImage() } - if($('.current.image').filter(':visible').length == 0){ + if(getImage().filter(':visible').length == 0){ centerView(focusImage(getImageBefore())) } centerRibbons() @@ -260,7 +260,7 @@ var KEYBOARD_CONFIG = { } else { prevImage() } - if($('.current.image').filter(':visible').length == 0){ + if(getImage().filter(':visible').length == 0){ centerView(focusImage(getImageBefore())) } centerRibbons() @@ -274,7 +274,7 @@ var KEYBOARD_CONFIG = { // XXX STUB -- replace with a real info window... default: doc('Show current image info', function(){ - var gid = getImageGID($('.current.image')) + var gid = getImageGID(getImage()) var r = getRibbonIndex(getRibbon()) var data = IMAGES[gid] var orientation = data.orientation diff --git a/ui/marks.js b/ui/marks.js index d2416590..956b4472 100755 --- a/ui/marks.js +++ b/ui/marks.js @@ -77,7 +77,7 @@ var toggleMarkedOnlyView = createCSSClassToggler('.viewer', // XXX shifting images and unmarking in this mode do not work correctly... var toggleMarkesView = createCSSClassToggler('.viewer', 'marks-visible', function(){ - var cur = $('.current.image') + var cur = getImage() // current is marked... if(cur.hasClass('marked')){ centerView(null, 'css') @@ -91,7 +91,7 @@ var toggleMarkesView = createCSSClassToggler('.viewer', 'marks-visible', } // get marked image from other ribbons... prevRibbon() - if($('.current.image').hasClass('marked')){ + if(getImage().hasClass('marked')){ return } nextRibbon() @@ -106,7 +106,7 @@ var toggleMarkesView = createCSSClassToggler('.viewer', 'marks-visible', var toggleImageMark = createCSSClassToggler('.current.image', 'marked', function(action){ toggleMarkesView('on') - $('.viewer').trigger('togglingMark', [$('.current.image'), action]) + $('.viewer').trigger('togglingMark', [getImage(), action]) }) @@ -162,7 +162,7 @@ function invertImageMarks(){ // XXX need to make this dynamic data compatible... function toggleImageMarkBlock(image){ if(image == null){ - image = $('.current.image') + image = getImage() } //$('.viewer').trigger('togglingImageBlockMarks', [image]) // we need to invert this...