degal.py
changeset 12 c2d8e9a754a1
parent 11 27dac27d1a58
child 24 001f52cd057e
equal deleted inserted replaced
11:27dac27d1a58 12:c2d8e9a754a1
    19 # Free Software Foundation, Inc.,
    19 # Free Software Foundation, Inc.,
    20 # 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    20 # 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    21 #
    21 #
    22 
    22 
    23 import os.path, os
    23 import os.path, os
    24 import urllib
    24 from optparse import OptionParser
    25 import logging
       
    26 import string
       
    27 from datetime import datetime
       
    28 import struct
       
    29 import base64
       
    30 import shelve
       
    31 
    25 
    32 import PIL
    26 from lib import folder, shorturl
    33 import PIL.Image
       
    34 
    27 
    35 import utils
    28 def main (dir='.', targets=()) :
    36 
       
    37 __version__ = '0.2'
       
    38 
       
    39 TEMPLATE_DIR='templates'
       
    40 TEMPLATE_EXT='html'
       
    41 
       
    42 logging.basicConfig(
       
    43     level=logging.INFO,
       
    44     format="%(message)s",
       
    45 #    format="%(name)8s %(levelname)8s   %(lineno)3d %(message)s",
       
    46 
       
    47 )
       
    48 
       
    49 tpl = logging.getLogger('tpl')
       
    50 index = logging.getLogger('index')
       
    51 render = logging.getLogger('render')
       
    52 
       
    53 tpl.setLevel(logging.WARNING)
       
    54 
       
    55 #for l in (tpl, index, prepare, render) :
       
    56 #    l.setLevel(logging.DEBUG)
       
    57 
       
    58 def readFile (path) :
       
    59     fo = open(path, 'r')
       
    60     data = fo.read()
       
    61     fo.close()
       
    62 
       
    63     return data
       
    64 
       
    65 class Template (object) :
       
    66     GLOBALS = dict(
       
    67         VERSION=__version__,
       
    68     )
       
    69 
       
    70     def __init__ (self, name) :
       
    71         self.name = name
       
    72         self.path = os.path.join(TEMPLATE_DIR, "%s.%s" % (name, TEMPLATE_EXT))
       
    73 
       
    74         tpl.debug("Template %s at %s", name, self.path)
       
    75 
       
    76         self.content = readFile(self.path)
       
    77 
       
    78     def render (self, **vars) :
       
    79         content = self.content
       
    80         
       
    81         vars.update(self.GLOBALS)
       
    82 
       
    83         for name, value in vars.iteritems() :
       
    84             content = content.replace('<!-- %s -->' % name, str(value))
       
    85 
       
    86         return content
       
    87 
       
    88     def renderTo (self, path, **vars) :
       
    89         tpl.info("Render %s to %s", self.name, path)
       
    90 
       
    91         fo = open(path, 'w')
       
    92         fo.write(self.render(**vars))
       
    93         fo.close()
       
    94 
       
    95 gallery_tpl = Template('gallery')
       
    96 image_tpl = Template('image')
       
    97 
       
    98 IMAGE_EXTS = ('jpg', 'jpeg', 'png', 'gif', 'bmp')
       
    99 
       
   100 THUMB_DIR = 'thumbs'
       
   101 PREVIEW_DIR = 'previews'
       
   102 TITLE_FILE = 'title.txt'
       
   103 
       
   104 THUMB_GEOM = (160, 120)
       
   105 PREVIEW_GEOM = (640, 480)
       
   106 
       
   107 DEFAULT_TITLE = 'Image gallery'
       
   108 
       
   109 # how many image/page
       
   110 IMAGE_COUNT = 50
       
   111 
       
   112 def isImage (fname) :
       
   113     """
       
   114         Is the given filename likely to be an image file?
       
   115     """
       
   116 
       
   117     fname = fname.lower()
       
   118     base, ext = os.path.splitext(fname)
       
   119     ext = ext.lstrip('.')
       
   120 
       
   121     return ext in IMAGE_EXTS
       
   122 
       
   123 def link (url, title) :
       
   124     """
       
   125         Returns an <a href=""></a> link tag with the given values
       
   126     """
       
   127 
       
   128     return "<a href='%s'>%s</a>" % (urllib.quote(url), title)
       
   129 
       
   130 def dirUp (count=1) :
       
   131     """
       
   132         Returns a relative path to the directly count levels above the current one
       
   133     """
       
   134 
       
   135     if not count :
       
   136         return '.'
       
   137 
       
   138     return os.path.join(*(['..']*count))
       
   139 
       
   140 def readTitleDescr (path) :
       
   141     """
       
   142         Read a title.txt or <imgname>.txt file
       
   143     """
       
   144 
       
   145     if os.path.exists(path) :
       
   146         content = readFile(path)
       
   147 
       
   148         if '---' in content :
       
   149             title, descr = content.split('---', 1)
       
   150         else :
       
   151             title, descr = content, ''
       
   152 
       
   153         return title.strip(), descr.strip()
       
   154 
       
   155     return None, None
       
   156 
       
   157 class Folder (object) :
       
   158     def __init__ (self, name='.', parent=None) :
       
   159         # the directory name
       
   160         self.name = name
       
   161 
       
   162         # our parent Folder, or None
       
   163         self.parent = parent
       
   164 
       
   165         # the path to this dir, as a relative path to the root of the image gallery, always starts with .
       
   166         if parent and name :
       
   167             self.path = parent.pathFor(name)
       
   168         else :
       
   169             self.path = name
       
   170 
       
   171         # the url-path to the index.html file
       
   172         self.html_path = self.path
       
   173         
       
   174         # dict of fname -> Folder
       
   175         self.subdirs = {}
       
   176 
       
   177         # dict of fname -> Image
       
   178         self.images = {}
       
   179         
       
   180         # our human-friendly title
       
   181         self.title = None
       
   182 
       
   183         # our long-winded description
       
   184         self.descr = ''
       
   185 
       
   186         # is this folder non-empty?
       
   187         self.alive = None
       
   188         
       
   189         # self.images.values(), but sorted by filename
       
   190         self.sorted_images = []
       
   191         
       
   192         # the ShortURL key to this dir
       
   193         self.shorturl_code = None
       
   194 
       
   195         # were we filtered out?
       
   196         self.filtered = False
       
   197    
       
   198     def pathFor (self, *fnames) :
       
   199         """
       
   200             Return a root-relative path to the given path inside this dir
       
   201         """
       
   202         return os.path.join(self.path, *fnames)
       
   203 
       
   204     def index (self, filters=None) :
       
   205         """
       
   206             Look for other dirs and images inside this dir. Filters must be either None,
       
   207             whereupon all files will be included, or a dict of {filename -> next_filter}.
       
   208             If given, only filenames that are present in the dict will be indexed, and in
       
   209             the case of dirs, the next_filter will be passed on to that Folder's index
       
   210             method.
       
   211         """
       
   212 
       
   213         index.info("Indexing %s", self.path)
       
   214 
       
   215         if filters :
       
   216             self.filtered = True
       
   217         
       
   218         # iterate through listdir
       
   219         for fname in os.listdir(self.path) :
       
   220             # the full filesystem path to it
       
   221             fpath = self.pathFor(fname)
       
   222             
       
   223             # ignore dotfiles
       
   224             if fname.startswith('.') :
       
   225                 index.debug("Skipping dotfile %s", fname)
       
   226                 continue
       
   227             
       
   228             # apply filters
       
   229             if filters :
       
   230                 if fname in filters :
       
   231                     next_filter = filters[fname]
       
   232                 else :
       
   233                     index.debug("Skip `%s' as we have a filter", fname)
       
   234                     continue
       
   235             else :
       
   236                 next_filter = None
       
   237                 
       
   238             # recurse into subdirs, but not thumbs/previews
       
   239             if os.path.isdir(fpath) and fname not in (THUMB_DIR, PREVIEW_DIR) :
       
   240                 index.debug("Found subdir %s", fpath)
       
   241                 f = self.subdirs[fname] = Folder(fname, self)
       
   242                 if f.index(next_filter) :   # recursion
       
   243                     # if a subdir is alive, we are alive as well
       
   244                     self.alive = True
       
   245 
       
   246             # handle images
       
   247             elif os.path.isfile(fpath) and isImage(fname) :
       
   248                 index.debug("Found image %s", fname)
       
   249                 self.images[fname] = Image(self, fname)
       
   250 
       
   251             # ignore everything else
       
   252             else :
       
   253                 index.debug("Ignoring file %s", fname)
       
   254         
       
   255         # sort and link the images
       
   256         if self.images :
       
   257             self.alive = True
       
   258 
       
   259             # sort the images
       
   260             fnames = self.images.keys()
       
   261             fnames.sort()
       
   262 
       
   263             prev = None
       
   264 
       
   265             # link
       
   266             for fname in fnames :
       
   267                 img = self.images[fname]
       
   268 
       
   269                 img.prev = prev
       
   270 
       
   271                 if prev :
       
   272                     prev.next = img
       
   273 
       
   274                 prev = img
       
   275                 
       
   276                 # add to the sorted images list
       
   277                 self.sorted_images.append(img)
       
   278 
       
   279         return self.alive
       
   280 
       
   281     def getObjInfo (self) :
       
   282         """
       
   283             Metadata for shorturls2.db
       
   284         """
       
   285         return 'dir', self.path, 'index'
       
   286 
       
   287     def linkTag (self) :
       
   288         """
       
   289             A text-link to this dir
       
   290         """
       
   291 
       
   292         return link(self.path, self.title)
       
   293 
       
   294     def breadcrumb (self) :
       
   295         """
       
   296             Returns a [(fname, title)] list of this dir's parent dirs
       
   297         """
       
   298 
       
   299         f = self
       
   300         b = []
       
   301         d = 0
       
   302         
       
   303         while f :
       
   304             b.insert(0, (dirUp(d), f.title))
       
   305 
       
   306             d += 1
       
   307             f = f.parent
       
   308         
       
   309         return b
       
   310 
       
   311     def inRoot (self, *fnames) :
       
   312         """
       
   313             Return a relative URL from this dir to the given path in the root dir
       
   314         """
       
   315 
       
   316         c = len(self.path.split('/')) - 1
       
   317 
       
   318         return os.path.join(*((['..']*c) + list(fnames)))
       
   319     
       
   320     def _page_fname (self, page) :
       
   321         assert page >= 0
       
   322 
       
   323         if page > 0 :
       
   324             return  'index_%d.html' % page
       
   325         else :
       
   326             return 'index.html'
       
   327 
       
   328     def render (self) :
       
   329         """
       
   330             Render the index.html, Images, and recurse into subdirs
       
   331         """
       
   332         
       
   333         # ded folders are skipped
       
   334         if not self.alive :
       
   335             render.info("Skipping dir %s", self.path)
       
   336             return
       
   337         
       
   338         # if this dir's contents were filtered out, then we can't render the index.html, as we aren't aware of all the images in here
       
   339         if self.filtered :
       
   340             render.warning("Dir `%s' contents were filtered, so we won't render the gallery index again", self.path)
       
   341 
       
   342         else :  
       
   343             # create the thumb/preview dirs if needed
       
   344             for dir in (THUMB_DIR, PREVIEW_DIR) :
       
   345                 path = self.pathFor(dir)
       
   346 
       
   347                 if not os.path.isdir(path) :
       
   348                     render.info("Creating dir %s", path)
       
   349                     os.mkdir(path)
       
   350 
       
   351             # figure out our title
       
   352             title_path = self.pathFor(TITLE_FILE)
       
   353             
       
   354             title, descr = readTitleDescr(title_path)
       
   355 
       
   356             if title :
       
   357                 self.title = title
       
   358                 self.descr = descr
       
   359             
       
   360             # default title for the root dir
       
   361             elif self.name == '.' :
       
   362                 self.title = 'Index'
       
   363 
       
   364             else :
       
   365                 self.title = self.name
       
   366 
       
   367             # sort the subdirs
       
   368             subdirs = self.subdirs.items()
       
   369             subdirs.sort()
       
   370             
       
   371             # generate the <a href=""></a>'s for the subdirs
       
   372             subdir_linkTags = [link(f.name, f.title) for fname, f in subdirs if f.alive]
       
   373             
       
   374             # stick them into a list
       
   375             if subdir_linkTags :
       
   376                 directories = "<ul>\n\t<li>%s</li>\n</ul>" % "</li>\n\t<li>".join(subdir_linkTags)
       
   377             else :
       
   378                 directories = ''
       
   379 
       
   380             render.info("Rendering %s", self.path)
       
   381 
       
   382             # paginate!
       
   383             images = self.sorted_images
       
   384             image_count = len(images)
       
   385             pages = []
       
   386             
       
   387             while images :
       
   388                 pages.append(images[:IMAGE_COUNT])
       
   389                 images = images[IMAGE_COUNT:]
       
   390 
       
   391             pagination_required = len(pages) > 1
       
   392 
       
   393             if pagination_required :
       
   394                 render.info("Index split into %d pages of %d images each", len(pages), IMAGE_COUNT)
       
   395             
       
   396             for cur_page, page in enumerate(pages) :
       
   397                 if pagination_required :
       
   398                     pagination = "<ul>" # <li>Showing Images %d - %d of %d</li>" % (cur_page*IMAGE_COUNT, cur_page*IMAGE_COUNT+len(page), image_count)
       
   399                     
       
   400                     if cur_page > 0 :
       
   401                         pagination += '<li><a href="%s">&laquo; Prev</a></li>' % (self._page_fname(cur_page - 1))
       
   402                     else :
       
   403                         pagination += '<li><span>&laquo; Prev</span></li>'
       
   404 
       
   405                     for x in xrange(0, len(pages)) :
       
   406                         if x == cur_page :
       
   407                             pagination += '<li><strong>%s</strong></li>' % (x + 1)
       
   408                         else :
       
   409                             pagination += '<li><a href="%s">%s</a></li>' % (self._page_fname(x), x+1)
       
   410 
       
   411                     if cur_page < len(pages) - 1 :
       
   412                         pagination += '<li><a href="%s">Next &raquo;</a></li>' % (self._page_fname(cur_page + 1))
       
   413                     else :
       
   414                         pagination += '<li><span>Next &raquo;</span></li>'
       
   415 
       
   416                     pagination += "</ul>"
       
   417                     shorturl = "%s/%s" % (self.shorturl_code, cur_page+1)
       
   418                 else :
       
   419                     pagination = ''
       
   420                     shorturl = self.shorturl_code
       
   421 
       
   422                 # render to index.html
       
   423                 gallery_tpl.renderTo(self.pathFor(self._page_fname(cur_page)), 
       
   424                     STYLE_URL=self.inRoot('style.css'),
       
   425                     BREADCRUMB=" &raquo; ".join([link(u, t) for (u, t) in self.breadcrumb()]),
       
   426                     TITLE=self.title,
       
   427                     DIRECTORIES=directories,
       
   428                     PAGINATION=pagination,
       
   429                     CONTENT="".join([i.thumbImgTag() for i in page]),
       
   430                     DESCR=self.descr,
       
   431                     SHORTURL=self.inRoot('s', shorturl),
       
   432                     SHORTURL_CODE=shorturl,
       
   433                 )
       
   434         
       
   435         # render images
       
   436         for img in self.images.itervalues() :
       
   437             img.render()
       
   438         
       
   439         # recurse into subdirs
       
   440         for dir in self.subdirs.itervalues() :
       
   441             dir.render()
       
   442                     
       
   443 class Image (object) :
       
   444     def __init__ (self, dir, name) :
       
   445         # the image filename, e.g. DSC3948.JPG
       
   446         self.name = name
       
   447 
       
   448         # the Folder object that we are in
       
   449         self.dir = dir
       
   450         
       
   451         # the relative path from the root to us
       
   452         self.path = dir.pathFor(name)
       
   453 
       
   454         # the basename+ext, e.g. DSCR3948, .JPG
       
   455         self.base_name, self.ext = os.path.splitext(name)
       
   456         
       
   457         # the root-relative paths to the thumb and preview images
       
   458         self.thumb_path = self.dir.pathFor(THUMB_DIR, self.name)
       
   459         self.preview_path = self.dir.pathFor(PREVIEW_DIR, self.name)
       
   460         
       
   461         # our user-friendly title
       
   462         self.title = name
       
   463 
       
   464         # our long-winded description
       
   465         self.descr = ''
       
   466 
       
   467         # the image before and after us, both may be None
       
   468         self.prev = self.next = None
       
   469         
       
   470         # the name of the .html gallery view thing for this image, *always* self.name + ".html"
       
   471         self.html_name = self.name + ".html"
       
   472 
       
   473         # the root-relative path to the gallery view
       
   474         self.html_path = self.dir.pathFor(self.html_name)
       
   475         
       
   476         #
       
   477         # Figured out after prepare
       
   478         #
       
   479 
       
   480         # (w, h) tuple
       
   481         self.img_size = None
       
   482         
       
   483         # the ShortURL code for this image
       
   484         self.shorturl_code = None
       
   485 
       
   486         # what to use in the rendered templates, intended to be overridden by subclasses
       
   487         self.series_act = "add"
       
   488         self.series_verb = "Add to"
       
   489     
       
   490     def getObjInfo (self) :
       
   491         """
       
   492             Metadata for shorturl2.db
       
   493         """
       
   494         return 'img', self.dir.path, self.name
       
   495 
       
   496     def thumbImgTag (self) :
       
   497         """
       
   498             a <a><img /></a> of this image's thumbnail. Path relative to directory we are in
       
   499         """
       
   500         return link(self.html_name, "<img src='%s' alt='%s' title='%s' />" % (os.path.join(THUMB_DIR, self.name), self.descr, self.title))
       
   501 
       
   502     def previewImgTag (self) :
       
   503         """
       
   504             a <a><img /></a> of this image's preview. Path relative to directory we are in
       
   505         """
       
   506         return link(self.name, "<img src='%s' alt='%s' title='%s' />" % (os.path.join(PREVIEW_DIR, self.name), self.descr, self.title))
       
   507 
       
   508     def linkTag (self) :
       
   509         """
       
   510             a <a></a> text-link to this image
       
   511         """
       
   512         return link(self.html_name, self.title)
       
   513 
       
   514     def breadcrumb (self) :
       
   515         """
       
   516             Returns a [(fname, title)] list of this image's parents
       
   517         """
       
   518 
       
   519         f = self.dir
       
   520         b = [(self.html_name, self.title)]
       
   521         d = 0
       
   522         
       
   523         while f :
       
   524             b.insert(0, (dirUp(d), f.title))
       
   525 
       
   526             d += 1
       
   527             f = f.parent
       
   528         
       
   529         return b
       
   530 
       
   531     def render (self) :
       
   532         """
       
   533             Write out the .html file
       
   534         """
       
   535         
       
   536         # stat the image file to get the filesize and mtime
       
   537         st = os.stat(self.path)
       
   538 
       
   539         self.filesize = st.st_size
       
   540         self.timestamp = st.st_mtime
       
   541         
       
   542         # open the image in PIL to get image attributes + generate thumbnails
       
   543         img = PIL.Image.open(self.path)
       
   544 
       
   545         self.img_size = img.size
       
   546 
       
   547         for out_path, geom in ((self.thumb_path, THUMB_GEOM), (self.preview_path, PREVIEW_GEOM)) :
       
   548             # if it doesn't exist, or it's older than the image itself, generate
       
   549             if not (os.path.exists(out_path) and os.stat(out_path).st_mtime > self.timestamp) :
       
   550                 render.info("Create thumbnailed image at %s with geom %s", out_path, geom)
       
   551                 
       
   552                 # XXX: is this the most efficient way to do this?
       
   553                 out_img = img.copy()
       
   554                 out_img.thumbnail(geom, resample=True)
       
   555                 out_img.save(out_path)
       
   556         
       
   557         # look for the metadata file
       
   558         title_path = self.dir.pathFor(self.base_name + '.txt')
       
   559         
       
   560         title, descr = readTitleDescr(title_path)
       
   561 
       
   562         if title :
       
   563             self.title = title
       
   564             self.descr = descr
       
   565 
       
   566         render.info("Rendering image %s", self.path)
       
   567 
       
   568         image_tpl.renderTo(self.html_path,
       
   569             STYLE_URL=self.dir.inRoot('style.css'),
       
   570             UP_URL=('.'),
       
   571             PREV_URL=(self.prev and self.prev.html_name or ''),
       
   572             NEXT_URL=(self.next and self.next.html_name or ''),
       
   573             FILE=self.name,
       
   574             BREADCRUMB=" &raquo; ".join([link(u, t) for u, t in self.breadcrumb()]),
       
   575             TITLE=self.title,
       
   576             PREVIOUS_THUMB=(self.prev and self.prev.thumbImgTag() or ''),
       
   577             IMAGE=self.previewImgTag(),
       
   578             NEXT_THUMB=(self.next and self.next.thumbImgTag() or ''),
       
   579             DESCRIPTION=self.descr,
       
   580             IMGSIZE="%dx%d" % self.img_size,
       
   581             FILESIZE=fmtFilesize(self.filesize),
       
   582             TIMESTAMP=fmtTimestamp(self.timestamp),
       
   583             SHORTURL=self.dir.inRoot('s', self.shorturl_code),
       
   584             SHORTURL_CODE=self.shorturl_code,
       
   585             SERIES_URL=self.dir.inRoot('series/%s/%s' % (self.series_act, self.shorturl_code)),
       
   586             SERIES_VERB=self.series_verb,
       
   587         )   
       
   588     
       
   589     def __str__ (self) :
       
   590         return "Image `%s' in `%s'" % (self.name, self.dir.path)
       
   591 
       
   592 def int2key (id) :
       
   593     """
       
   594         Turn an integer into a short-as-possible url-safe string
       
   595     """
       
   596     for type in ('B', 'H', 'I') :
       
   597         try :
       
   598             return base64.b64encode(struct.pack(type, id), '-_').rstrip('=')
       
   599         except struct.error :
       
   600             continue
       
   601 
       
   602     raise Exception("ID overflow: %s" % id)
       
   603 
       
   604 def updateShorturlDb (root) :
       
   605     """
       
   606         DeGAL <= 0.2 used a simple key => path mapping, but now we use
       
   607         something more structured, key => (type, dirpath, fname), where
       
   608 
       
   609         type    - one of 'img', 'dir'
       
   610         dirpath - the path to the directory, e.g. '.', './foobar', './foobar/quux'
       
   611         fname   - the filename, one of '', 'DSC9839.JPG', 'this.png', etc.
       
   612     """
       
   613 
       
   614     db = shelve.open('shorturls2', 'c', writeback=True)
       
   615     
       
   616     id = db.get('_id', 1)
       
   617 
       
   618     dirqueue = [root]
       
   619 
       
   620     # dict of path -> obj
       
   621     paths = {}
       
   622 
       
   623     index.info("Processing ShortURLs...")
       
   624 
       
   625     while dirqueue :
       
   626         dir = dirqueue.pop(0)
       
   627 
       
   628         dirqueue.extend(dir.subdirs.itervalues())
       
   629 
       
   630         if dir.alive :
       
   631             paths[dir.path] = dir
       
   632 
       
   633         for img in dir.images.itervalues() :
       
   634             paths[img.path] = img
       
   635 
       
   636     for key in db.keys() :
       
   637         if key.startswith('_') :
       
   638             continue
       
   639 
       
   640         type, dirpath, fname = db[key]
       
   641         
       
   642         path = os.path.join(dirpath, fname).rstrip('/')
       
   643 
       
   644         try :
       
   645             paths.pop(path).shorturl_code = key
       
   646             index.debug("Code for `%s' is %s", path, key)
       
   647 
       
   648         except KeyError :
       
   649             index.debug("Path `%s' in DB does not exist?", path)
       
   650 
       
   651     for obj in paths.itervalues() :
       
   652         key = int2key(id)
       
   653         id += 1
       
   654         
       
   655         index.info("Alloc code `%s' for `%s'", key, obj.html_path)
       
   656 
       
   657         obj.shorturl_code = key
       
   658 
       
   659         db[key] = obj.getObjInfo()
       
   660 
       
   661     db['_id'] = id
       
   662     db.close()
       
   663 
       
   664 def main (targets=()) :
       
   665     root_filter = {}
    29     root_filter = {}
   666 
    30 
   667     for target in targets :
    31     for target in targets :
   668         f = root_filter
    32         f = root_filter
   669         for path_part in target.split('/') :
    33         for path_part in target.split('/') :
   670             if path_part :
    34             if path_part :
   671                 if path_part not in f :
    35                 if path_part not in f :
   672                     f[path_part] = {}
    36                     f[path_part] = {}
       
    37                     
   673                 f = f[path_part]
    38                 f = f[path_part]
   674     
       
   675     index.debug('Filter: %s', root_filter)
       
   676 
    39 
   677     root = Folder()
    40     root = folder.Folder(dir)
   678     root.index(root_filter)
    41     root.index(root_filter)
   679     updateShorturlDb(root)
    42     shorturl.updateDB(root)
   680     root.render()
    43     root.render()
   681 
    44 
   682 def fmtFilesize (size) :
       
   683     return utils.formatbytes(size, forcekb=False, largestonly=True, kiloname='KiB', meganame='MiB', bytename='B', nospace=False)
       
   684 
       
   685 def fmtTimestamp (ts) :
       
   686     return datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S")
       
   687 
       
   688 if __name__ == '__main__' :
    45 if __name__ == '__main__' :
   689     from sys import argv
    46     parser = OptionParser(usage="usage: %prog [options] ... [target ...]")
   690     argv.pop(0)
    47     
   691 
    48     parser.add_option("-d", "--dir", dest="dir", help="look for images in DIR and write the HTML there", metavar="DIR", default=".")
   692     main(argv)
    49     
   693 
    50     options, filter_targets = parser.parse_args()
       
    51     
       
    52     main(options.dir, filter_targets)