#!/usr/bin/env python # # Copyright 2012 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 from math import ceil from datetime import datetime # (c) ville.rantanen@helsinki.fi __version__='1.6' webfilesearch=re.compile('.*index.html$|.*gallerystyle.css$|.*galleryscript.js$|.*descriptions.csv$|\..*',re.I) imagesearch=re.compile('.*\.jpg$|.*\.jpeg$|.*\.gif$|.*\.png$|.*\.svg$|.*\.pdf$',re.I) vectorsearch=re.compile('.*\.svg$|.*\.pdf$',re.I) #gifsearch=re.compile('.*gif$',re.I) excludepaths=re.compile('_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): ''' 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.timesort: imgs.sort(key=lambda f: os.path.getmtime(os.path.join(path, f)),reverse=options.reverse) else: imgs.sort(reverse=options.reverse) return imgs 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 getnonimagelist(path): ''' 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) return files def getpathlist(path): ''' 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.timesort: paths.sort(key=lambda f: os.path.getmtime(f),reverse=options.reverse) else: paths.sort(reverse=options.reverse) 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 ''' 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 ''' 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: nice=nicestring(i) size=sizestring(os.path.getsize(os.path.join(path,i))) strout+=''+unicode(nice,encoding="utf8").encode('ascii', 'xmlcharrefreplace')+' ['+size+']' n+=1 strout+='
' return strout def createthumbs(path,list): ''' 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='1000' for i in list: outmedium=os.path.join(path,'_med','med_'+i+'.jpg') outthumb=os.path.join(path,'_tn','tn_'+i+'.jpg') inpath=os.path.join(path,i) if (not os.path.exists(outmedium)): print('Medium.. '+i+' '+str(n)+'/'+str(nsum)) create_medium_bitmap(inpath,outmedium,r,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)) n+=1 return def create_medium_bitmap(infile,outfile,r,vector=False): 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): if vector: convargs=['convert','-density','300x300',infile,'-background','white','-flatten','-thumbnail','90x90^','-gravity','Center','-crop','90x90+0+0','+repage','-quality','75',outfile] else: convargs=['convert','-define','jpeg:size=300x300',infile,'-background','white','-flatten','-thumbnail','90x90^','-gravity','Center','-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,'descriptions.csv')): return list desc=[i for i in list] reader = csv.reader(open(os.path.join(path,'descriptions.csv'),'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 crumblinks(crumbs): ''' Create the HTML string for crumb trails ''' strout='
' i=1 for c in crumbs: cname=os.path.basename(c) if i==1: cname="Home" 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 traverse(path,crumbs): ''' The recursive main function to create the index.html and seek sub folders ''' print(path) if len(crumbs)==1: header=getheader(path,'../'*(len(crumbs)-1),inputs[0][1]) else: header=getheader(path,'../'*(len(crumbs)-1)) print(len(crumbs)) pathlist=getpathlist(path) imagelist=getimagelist(path) filelist=getnonimagelist(path) print(str(len(pathlist))+' paths') print(str(len(imagelist))+' images') print(str(len(filelist))+' other files') crumbstring=crumblinks(crumbs) if len(pathlist)>0: pathstring=pathlinks(path,pathlist) pathjs=pathscript(path,pathlist) else: pathstring='
' pathjs='' filestring=filelinks(path,filelist) filejs=filescript(path,filelist) if len(imagelist)>0: imagestring=imagelinks(path,imagelist) imagejs=imagescript(path,imagelist) else: imagestring='
' imagejs='' 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('
') f.write('
') f.write(imagestring) f.write(filestring) f.write('
') f.write(getfooter()) f.close() createthumbs(path,imagelist) for p in pathlist: nextcrumbs=[i for i in crumbs] nextcrumbs.append(os.path.join(path,p)) traverse(os.path.join(path,p),nextcrumbs) return class AndurilOptions: '''Object featuring same variables as arguments from command line.''' def __init__(self, timesort, reverse): '''Initializes the person's data.''' self.timesort=timesort self.reverse=reverse self.attachments=True def execute(cf): global inputs global options inputs=[] inputs.append((cf.get_input('folderRoot'),'',cf.get_input('csvRoot'))) for i in range(8): inputs.append((cf.get_input('folder'+str(i+1)),cf.get_parameter('title'+str(i+1)),cf.get_input('csv'+str(i+1)))) fileCol=cf.get_parameter('fileCol') annotationCol=cf.get_parameter('annotationCol') options=AndurilOptions(cf.get_parameter('sortTime','boolean'), cf.get_parameter('sortReverse','boolean')) outDir = cf.get_output('gallery') # the folderRoot demands that the outDir is not created earlier. if inputs[0][0]==None: os.mkdir(outDir) else: shutil.copytree(inputs[0][0],os.path.join(outDir,inputs[0][1])) for d in inputs: if (d[0]==None): continue if not os.path.exists(d[0]): continue print('Copying gallery '+d[1]) cf.write_log('Copying gallery '+d[1]) if not os.path.exists(os.path.join(outDir,d[1])): shutil.copytree(d[0],os.path.join(outDir,d[1])) # Find the annotations if (d[2] is not None): reader = csv.DictReader(open(d[2],'rb'), delimiter='\t', doublequote=False, escapechar='\\', quoting=csv.QUOTE_ALL) for row in reader: break if (fileCol not in reader.fieldnames): cf.write_error("Column \""+fileCol+"\" not found.") exit() if (annotationCol not in reader.fieldnames): cf.write_error("Column \""+annotationCol+"\" not found.") exit() reader = csv.DictReader(open(d[2],'rb'), delimiter='\t', doublequote=False, escapechar=None, quotechar='"', quoting=csv.QUOTE_NONE) annotations=[] header=['file','description'] for row in reader: annotations.append( (stripquotes.sub('',row.get('"'+fileCol+'"')), stripquotes.sub('',row.get('"'+annotationCol+'"')) ) ) writefid=open(os.path.join(outDir,d[1],'descriptions.csv'),'wb') writer = csv.writer(writefid, delimiter='\t', doublequote=False,escapechar='\\', quotechar='"', quoting=csv.QUOTE_NONE) writer.writerow(header) for r in annotations: writer.writerow([r[0],r[1]]) writefid.close() # Copy all resources to target folder shutil.copyfile('gallerystyle.css',os.path.join(outDir,'gallerystyle.css')) shutil.copyfile('galleryscript.js',os.path.join(outDir,'galleryscript.js')) inputs[0]=((cf.get_input('folderRoot'),cf.get_parameter('titleRoot'),cf.get_input('csvRoot'))) startpath=os.path.abspath(outDir) traverse(startpath,[startpath]) return 0 def execute_plain(): ''' Execute this if run outside anduril ''' from optparse import OptionParser usage='''Usage: %prog [options] folder folder is the root folder of the image album.''' parser=OptionParser(usage=usage,version=__version__) parser.add_option("-r",action="store_true",dest="reverse",default=False, help="Reverse sort orded") parser.add_option("-t",action="store_true",dest="timesort",default=False, help="Sort by file modification time") parser.add_option("-a",action="store_false",dest="attachments",default=True, help="Disable attachments") global options (options,args)=parser.parse_args() if len(args) != 1: #sys.exit(0) startpath=os.path.abspath('.') else: startpath=os.path.abspath(args[0]) pathname=os.path.dirname(os.path.realpath(sys.argv[0])) fullpath=os.path.abspath(pathname) # Copy all resources to target folder shutil.copyfile(os.path.join(fullpath,'gallerystyle.css'),os.path.join(startpath,'gallerystyle.css')) shutil.copyfile(os.path.join(fullpath,'galleryscript.js'),os.path.join(startpath,'galleryscript.js')) global inputs inputs=[] inputs.append((None,'Gallery',None)) traverse(startpath,[startpath]) return try: import component_skeleton.main except ImportError: execute_plain() sys.exit(0) component_skeleton.main.main(execute)