#!/usr/bin/env python # # Copyright 2016 Ville Rantanen # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . import sys,os import re import urllib import shutil import csv import subprocess import string from math import ceil from datetime import datetime # (c) ville.q.rantanen@gmail.com __version__='2.20161005' FILECONFIG=".config" FILEDESC="descriptions.csv" FILEINFO="info.txt" SAVEDCONFIG="""attachments=boolean gallery=string infofile=string parent=string reverse=boolean timesort=boolean clean=boolean force=boolean gravity=string link=boolean thumbs=boolean width=string""".split('\n') CONFIGCOMMENTS=""" config values: gallery: Name of the gallery infofile: Name of the infofile, inserted in beginning of the main page parent: String URL pointing to parent folder reverse: Sort reverse timesort: Sort by timestamp clean: Delete unused thumbnails force: Force recreate thumbnails gravity: ImageMagick option for creating thumbnails, e.g. North,East,Center link: Medium sized images are symbolic links to original thumbs: Build medium sized and thumbnail images. width: Medium images longer axis in pixels """.split('\n') MISSINGICON="data:image/png;base64,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" FAVICON="data:image/png;base64,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" # python -c 'print open("icon.png", "rb").read().encode("base64").replace("\n","")' webfilesearch=re.compile('.*index.html$|'+FILEDESC+'$|^'+FILEINFO+'$|\..*',re.I) imagesearch=re.compile('.*\.jpg$|.*\.jpeg$|.*\.gif$|.*\.png$|.*\.tif$|.*\.svg$|.*\.pdf$',re.I) vectorsearch=re.compile('.*\.svg$|.*\.pdf$',re.I) nonconvertiblesearch=re.compile('.*\.html$|.*\.htm$|.*\.php$',re.I) #gifsearch=re.compile('.*gif$',re.I) excludepaths=re.compile('_med|_tn|.med|.tn|\..*') doublequotes=re.compile('"') singlequotes=re.compile("'") stripquotes=re.compile('^"|"$') def getheader(path,parent,title=""): if title=="": title=unicode(os.path.basename(path),encoding="utf8").encode('ascii', 'xmlcharrefreplace') return ''' '''+title+''' ''' def getfooter(): return ''' ''' def getimagelist(path,options=False): ''' Returns a list of images matching the regex ''' list=os.listdir(path) imgs=[] for f in list: if (imagesearch.match(f)) and (os.path.isfile(os.path.join(path,f))): imgs.append(f) if options: if options.timesort: imgs.sort(key=lambda f: os.path.getmtime(os.path.join(path, f)),reverse=options.reverse) else: imgs.sort(reverse=options.reverse,key=lambda x: natural_sort_key(x)) else: imgs.sort(key=lambda x: natural_sort_key(x)) return imgs def getnonconvertiblelist(path,options=False): ''' Returns a list of files matching the nonconvertible regex ''' list=os.listdir(path) files=[] for f in list: if (nonconvertiblesearch.match(f)) and (os.path.isfile(os.path.join(path,f))) and not (webfilesearch.match(f)): files.append(f) if options: if options.timesort: files.sort(key=lambda f: os.path.getmtime(os.path.join(path, f)),reverse=options.reverse) else: files.sort(reverse=options.reverse,key=lambda x: natural_sort_key(x)) else: files.sort(key=lambda x: natural_sort_key(x)) return files def getfiletimes(path,list): ''' Returns a list of modification times ''' times=[] for p in list: times.append(int(os.path.getmtime(os.path.join(path,p)))) return times def getfilesizes(path,list): ''' Returns a list of sizes ''' sizes=[] for p in list: sizes.append(int(os.path.getsize(os.path.join(path,p)))) return sizes def getnonimagelist(path,options): ''' Returns a list of files not matching the image match regex ''' list=os.listdir(path) files=[] if not options.attachments: return files for f in list: if (not webfilesearch.match(f)) and (not imagesearch.match(f)) and (os.path.isfile(os.path.join(path,f))): files.append(f) if options.timesort: files.sort(key=lambda f: os.path.getmtime(os.path.join(path, f)),reverse=options.reverse) else: files.sort(reverse=options.reverse,key=lambda x: natural_sort_key(x)) return files def getpathlist(path,options=False): ''' Returns a list of subfolders not matching the exclusion regex ''' list=os.listdir(path) paths=[] for d in list: if (not excludepaths.match(d)) and (os.path.isdir(os.path.join(path,d))): paths.append(d) if options: if options.timesort: paths.sort(key=lambda f: os.path.getmtime(os.path.join(path,f)),reverse=options.reverse) else: paths.sort(reverse=options.reverse,key=lambda x: natural_sort_key(x)) else: paths.sort(key=lambda x: natural_sort_key(x)) return paths def pathscript(path,list): ''' Returns the javascript string of pathlist and pathimage arrays ''' scrstr='' return scrstr def pathlinks(path,list): ''' Returns the HTML string of subfolders ''' if len(list)==0: return '
' pathstr='
' pathstr+='

Subfolders

' for p in list: nice=nicestring(p) imglist=getimagelist(os.path.join(path,p)) nsum=str(len(imglist)) imgstr="" if len(imglist)>0: imgstr='' else: imgstr='' pathstr+=''+imgstr+''+unicode(nice,encoding="utf8").encode('ascii', 'xmlcharrefreplace')+' ('+nsum+')'; pathstr+='' pathstr+='
' return pathstr def imagescript(path,list): ''' Returns the javascript string of imagelist and imagedesc ''' strout='' return strout def imagelinks(path,list): ''' Returns the HTML string of images ''' if len(list)==0: return '
' strout='
' return strout def filescript(path,list): ''' Returns the javascript string of filelist ''' strout='' return strout def filelinks(path,list): ''' Returns the HTML string of non image files ''' strout='
' if len(list)>0: strout+='

Attachments

' n=0 for i in list: size=sizestring(os.path.getsize(os.path.join(path,i))) strout+=''+unicode(i,encoding="utf8").encode('ascii', 'xmlcharrefreplace')+' ['+size+']' n+=1 strout+='
' return strout def cleanthumbs(path): ''' clears .med and .tn for unused thumbs ''' print('clearing unused thumbs...') if os.path.exists(os.path.join(path,'.tn')): clearfolder(path,os.path.join(path,'.tn'),re.compile("(.*)(.jpg)")) if os.path.exists(os.path.join(path,'.med')): clearfolder(path,os.path.join(path,'.med'),re.compile("(.*)(.jpg)")) return def clearfolder(path,tnpath,regex): ''' clears given folder ''' list=getimagelist(tnpath) for i in list: f=regex.match(i) try: if not os.path.exists(os.path.join(path,f.group(1))): print('removing '+i) os.remove(os.path.join(tnpath,i)) except: continue return def createthumbs(path,list,options): ''' Runs imagemagick Convert to create medium sized and thumbnail images ''' if len(list)==0: return if not os.path.exists(os.path.join(path,'.tn')): os.mkdir(os.path.join(path,'.tn')) if not os.path.exists(os.path.join(path,'.med')): os.mkdir(os.path.join(path,'.med')) n=1 nsum=len(list) r=str(options.width) res=r+'x'+r+'>' for i in list: outmedium=os.path.join(path,'.med',i+'.jpg') outthumb=os.path.join(path,'.tn',i+'.jpg') inpath=os.path.join(path,i) if (options.force) and os.path.exists(outmedium): os.unlink(outmedium) if (options.force) and os.path.exists(outthumb): os.unlink(outthumb) if (not os.path.exists(outmedium)): print('Medium.. '+i+' '+str(n)+'/'+str(nsum)) create_medium_bitmap(inpath,outmedium,r,link=options.link,vector=vectorsearch.match(i)) if (not os.path.exists(outthumb)): print('Thumbnail.. '+i+' '+str(n)+'/'+str(nsum)) create_thumb_bitmap(outmedium,outthumb,vector=vectorsearch.match(i),gravity=options.gravity) n+=1 return def create_medium_bitmap(infile,outfile,r,link=False,vector=False): if link: os.symlink('../'+os.path.basename(infile),outfile) return res=r+'x'+r+'>' if vector: convargs=['convert','-density','300x300',infile+'[0]','-background','white','-flatten','-resize',res,'-quality','97',outfile] else: convargs=['convert','-define','jpeg:size='+r+'x'+r,infile+'[0]','-background','white','-flatten','-resize',res,'-quality','85',outfile] convp=subprocess.call(convargs) return def create_thumb_bitmap(infile,outfile,vector=False,gravity='Center'): if vector: convargs=['convert','-density','300x300',infile,'-background','white','-flatten','-thumbnail','90x90^','-gravity',gravity,'-crop','90x90+0+0','+repage','-quality','75',outfile] else: convargs=['convert','-define','jpeg:size=300x300',infile,'-background','white','-flatten','-thumbnail','90x90^','-gravity',gravity,'-crop','90x90+0+0','+repage','-quality','75',outfile] convp=subprocess.call(convargs) return def getdescriptions(path,list): ''' Read descriptions.csv file and returns a list of descriptions. Missing descriptions are replaced with the file name. ''' if not os.path.exists(os.path.join(path,FILEDESC)): return list desc=[i for i in list] reader = csv.reader(open(os.path.join(path,FILEDESC),'rb'), delimiter='\t', doublequote=False, escapechar='\\', quoting=csv.QUOTE_NONE) for row in reader: if len(row)>1: if row[0] in list: i=list.index(stripquotes.sub('',row[0])) desc[i]=stripquotes.sub('',row[1]) return desc def getinfo(path,options): ''' Read info.txt file and returns the content. Missing info file returns empty string. ''' if not os.path.exists(os.path.join(path,options.infofile)): return '' reader = open(os.path.join(path,options.infofile),'r') return unicode(reader.read(),encoding="utf8",errors="ignore").encode('ascii','xmlcharrefreplace') def crumblinks(crumbs,title,parent): ''' Create the HTML string for crumb trails ''' strout='
' if parent: if not parent.startswith('http://'): parent="../"*(len(crumbs))+parent strout+=''+'Home'.encode('ascii', 'xmlcharrefreplace')+': ' i=1 for c in crumbs: cname=os.path.basename(c) if i==1: cname=title cdepth=len(crumbs)-i clink="../"*cdepth strout+=''+unicode(cname,encoding="utf8").encode('ascii', 'xmlcharrefreplace')+': ' i+=1 strout+='
' return strout def nicestring(s): ''' Returns a nice version of a long string ''' if len(s)<20: return s s=s.replace("_"," ") s=s.replace("-"," ") if len(s)>30: s=s[0:26]+".."+s[-3:] return s def sizestring(size): ''' Returns human readable file size string ''' for x in ['b','kb','Mb','Gb','Tb']: if size < 1024.0: if (x=='b') | (x=='kb'): return "%d%s" % (size, x) else: return "%3.1f%s" % (size, x) size /= 1024.0 def natural_sort_key(s, _nsre=re.compile('([0-9]+)')): ''' Natural sort / Claudiu@Stackoverflow ''' return [int(text) if text.isdigit() else text.lower() for text in re.split(_nsre, s)] def which(program): ''' emulate shell which command ''' def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None def traverse(path,crumbs,inputs,options): ''' The recursive main function to create the index.html and seek sub folders ''' print(path) if (not options.recurselink) and (os.path.islink(path)): print('Not recursing, is a link') return if len(crumbs)==1: header=getheader(path,'../'*(len(crumbs)-1),inputs[0][1]) else: header=getheader(path,'../'*(len(crumbs)-1)) if not os.path.exists(os.path.join(path,'../'*(len(crumbs)-1),'.qalbum','gallery.js')): print('Warning, no (relative path) galleryscript! '+os.path.join(path,'../'*(len(crumbs)-1),'.qalbum','gallery.js')) pathlist=getpathlist(path,options) imagelist=getimagelist(path,options) if options.clean: cleanthumbs(path) if options.thumbs: createthumbs(path,imagelist,options) imagelist.extend(getnonconvertiblelist(path,options)) filelist=getnonimagelist(path,options) print(str(len(pathlist))+' paths, '+str(len(imagelist))+' images, '+str(len(filelist))+' other files') crumbstring=crumblinks(crumbs,options.gallery,options.parent) pathjs=pathscript(path,pathlist) pathstring=pathlinks(path,pathlist) filestring=filelinks(path,filelist) #filejs=filescript(path,filelist) # Filelist is not currently used in javascript imagestring=imagelinks(path,imagelist) imagejs=imagescript(path,imagelist) f=open(os.path.join(path,"index.html"),"w") f.write(header) f.write('
') f.write(pathjs) f.write(imagejs) #f.write(filejs) f.write(crumbstring) f.write(pathstring) f.write('
'+getinfo(path,options)+'
') f.write('
') f.write(imagestring) f.write(filestring) f.write('
') f.write(getfooter()) f.close() for p in pathlist: nextcrumbs=[i for i in crumbs] nextcrumbs.append(os.path.join(path,p)) traverse(os.path.join(path,p),nextcrumbs,inputs,options) return def setupoptions(): ''' Setup the command line options ''' from argparse import ArgumentParser parser=ArgumentParser() parser.add_argument("-v",action='version', version=__version__) parser.add_argument("--version",action='version', version=__version__) parser.add_argument("-c",action="store_true",dest="writeconfig",default=False, help="Write current configuration to file "+FILECONFIG+ " and exit. If file exists, settings read from the file, "+ "overriding switches.") parser.add_argument("-r",action="store_true",dest="reverse",default=False, help="Reverse sort orded") parser.add_argument("-L",action="store_false",dest="recurselink",default=True, help="List, but do not recurse in to symbolic link folders") parser.add_argument("-s",type=str,dest="style", help="User defined CSS style file.") parser.add_argument("-t",action="store_true",dest="timesort",default=False, help="Sort by file modification time") parser.add_argument("-a",action="store_false",dest="attachments",default=True, help="Disable attachments") parser.add_argument("-i",type=str,dest="infofile",default=FILEINFO, help="File name for info files in all folders. (Default: %(default)s)") parser.add_argument("-g",type=str,dest="gallery",default="Gallery", help="Name for the root gallery (Default: %(default)s)") parser.add_argument("--gravity",type=str,dest="gravity",default="Center", help="ImageMagick gravity for cropping. (Default: %(default)s)") parser.add_argument("-w",type=int,dest="width",default=850, help="Medium image size (Default: %(default)s)") parser.add_argument("--no-thumbs",action="store_false",dest="thumbs",default=True, help="Disable thumbnail and medium generation. Build the indexes only.") parser.add_argument("-p",type=str,dest="parent", help="Add a ../[PARENT] link to point out from the gallery. If the string starts with http:// it is considered as a static URL, otherwise the relative parent path is assumed.") parser.add_argument("startpath",type=str,action="store",default=os.path.abspath('.'),nargs='?', help="Root path of the gallery") options=parser.parse_args() options.startpath=os.path.abspath(options.startpath) options=setupdefaultoptions(options) return options def setupdefaultoptions(options): ''' Adds the missing options for the options object ''' if not which('convert'): print('You don\'t seem to have ImageMagick "convert" in PATH!') sys.exit(1) if 'attachments' not in options: options.attachments=True if 'clean' not in options: options.clean=False if 'force' not in options: options.force=False if 'infofile' not in options: options.infofile=FILEINFO if 'gallery' not in options: options.gallery="Gallery" if 'gravity' not in options: options.gravity="Center" if 'link' not in options: options.link=False if 'recursive' not in options: options.recursive=True if 'recurselink' not in options: options.recurselink=True if 'reverse' not in options: options.reverse=False if 'style' not in options or options.style is None: options.style=os.path.join(os.path.abspath(os.path.dirname(os.path.realpath(sys.argv[0]))),'lib','style.css') if 'timesort' not in options: options.timesort=False if 'thumbs' not in options: options.thumbs=True if 'width' not in options: options.width=850 return options def readconfig(options): """ Set up the options via config file """ if os.path.exists(FILECONFIG): import configobj try: cfg=configobj.ConfigObj(FILECONFIG, configspec=SAVEDCONFIG, unrepr=True) except configobj.UnreprError as err: print("Config file "+FILECONFIG+" syntax error!") print(err) sys.exit(1) for opt in cfg.keys(): setattr(options,opt,cfg[opt]) print("Read config from file") return options def writeconfig(options): """ Write the options to config file """ import configobj cfg=configobj.ConfigObj(configspec=SAVEDCONFIG, unrepr=True) cfg.initial_comment=CONFIGCOMMENTS cfg.filename=FILECONFIG for opt in SAVEDCONFIG: optname=opt.split("=")[0] cfg[optname]=getattr(options,optname) cfg.write() print('Wrote '+FILECONFIG) def execute_plain(): ''' Main execution function ''' options=setupoptions() options=readconfig(options) options=setupdefaultoptions(options) if options.writeconfig: writeconfig(options) sys.exit(0) # Copy all resources to target folder pathname=os.path.dirname(os.path.realpath(sys.argv[0])) fullpath=os.path.abspath(pathname) libpath=os.path.join(options.startpath,'.qalbum') if not os.path.exists(options.style): raise IOError('File not found: "'+options.style+'"') if not os.path.isdir(libpath): os.mkdir(libpath) shutil.copyfile(options.style,os.path.join(libpath,'style.css')) for jslib in ('gallery.js','jquery.js','jqzoom.js','touch.js'): shutil.copyfile(os.path.join(fullpath,'lib',jslib),os.path.join(libpath,jslib)) inputs=[] inputs.append((None,options.gallery,None)) traverse(options.startpath,[options.startpath],inputs,options) return if __name__ == "__main__": execute_plain()