PageRenderTime 87ms CodeModel.GetById 37ms RepoModel.GetById 0ms app.codeStats 0ms

/gallerygen.py

http://useless-scripts.googlecode.com/
Python | 413 lines | 407 code | 3 blank | 3 comment | 0 complexity | 8a89fd0c2fabc42baaecec4ade2d83ff MD5 | raw file
  1. #!/usr/bin/env python2.6
  2. # -*- coding: utf-8 -*-
  3. # $Id: gallerygen.py 51 2009-01-28 08:40:40Z urzenia $
  4. from __future__ import print_function
  5. __version__ = 'version 0.1'
  6. __author__ = 'Marcin ``MySZ`` Sztolcman <marcin@urzenia.net>'
  7. __copyright__ = '(r) 2008 - 2009'
  8. __program__ = 'gallerygen.py - simple tool for fast creating static image gallery'
  9. __date__ = '2009-01-28'
  10. __license__ = 'GPL v.2'
  11. __desc__ = '''%(desc)s
  12. %(author)s %(copyright)s
  13. license: %(license)s
  14. version %(version)s (%(date)s)''' % {
  15. 'desc': __program__,
  16. 'author': __author__,
  17. 'copyright': __copyright__,
  18. 'license': __license__,
  19. 'version': __version__,
  20. 'date': __date__
  21. }
  22. import commands
  23. import math
  24. import os, os.path
  25. import re
  26. import sys
  27. import time
  28. ## configuration
  29. class Config (dict):
  30. def __repr__ (self):
  31. return "\n".join ( '%s: %s' % (c, getattr (self, c)) for c in sorted (dir (self)) if not c.startswith ('_') and not callable (getattr (self, c)))
  32. def load (self, path):
  33. import ConfigParser
  34. cfg = ConfigParser.RawConfigParser ()
  35. cfg.read ((path, ))
  36. ## first templates:
  37. if cfg.has_section ('templates'):
  38. for k, v in cfg.items ('templates'):
  39. setattr (self, k, v)
  40. ## and the rest:
  41. if cfg.has_section ('config'):
  42. for option in cfg.options ('config'):
  43. if option in ('th_width', 'th_height', 'th_rows', 'th_cols'):
  44. setattr (self, option, cfg.getint ('config', option))
  45. elif option == 'images':
  46. setattr (self, option, re.compile (cfg.get ('config', 'images'), re.I))
  47. else:
  48. setattr (self, option, cfg.get ('config', option))
  49. config = None
  50. th_width = 200
  51. th_height = 200
  52. th_rows = 3
  53. th_cols = 3
  54. single_width = 1024
  55. single_height = 768
  56. big_width = 0
  57. big_height = 0
  58. directory = None
  59. images = re.compile (r'\.(?:jpe?g|gif|png)$', re.I)
  60. sharpen = 1
  61. skip_images = False
  62. title = 'Gallery - %(date)s - %(pageno)s'
  63. css ='''
  64. * { margin: 0; padding: 0; border: 0; }
  65. body { font-size: 62.5%; /*10px*/ background-color: #ccc; font-family: Tahoma, Arial, Helvetica, sans-serif; color: black; }
  66. a { color: red; text-decoration: none; }
  67. a:visited { color: #888; }
  68. a:hover { color: blue; text-decoration: underline; }
  69. br.clear, li.clear { clear: both; }
  70. br.clear { display: inline; height: 0.1px; }
  71. #header { position: relative; }
  72. #header h1, #navi { border-bottom: 4px solid #666; border-left: 4px solid #666; margin-bottom: 0em; height: 3.5em; float: right; }
  73. #header h1 { float: left; border-bottom: 4px solid #666; border-right: 4px solid #666; border-left: none; height: 3em; margin-bottom: none; }
  74. #navi li { float: right; list-style-type: none; height: 3em; width: 5em; }
  75. #navi_prev { text-align: right; }
  76. #navi_next { text-align: left; }
  77. #navi #navi_top { text-align: center; width: 1em; }
  78. #header h1, #navi li { font-size: 3em; padding: 3px 1.5em; }
  79. #thumbs { margin: 0 auto; }
  80. #thumbs td { padding: 10px; text-align: center; }
  81. #thumbs img { padding: 3px; border: 3px solid #999; opacity: 0.6; }
  82. #thumbs img:hover { border-color: #000; border-bottom-color: #000; opacity: 1; }
  83. #single { margin: 0 auto; padding-top: 1px; position: relative; }
  84. #single #img_name { width: 1em; padding-bottom: 1em; vertical-align: top; }
  85. #single #img_desc { padding: 0 2em 1em 2em; vertical-align: bottom; font-size: 2em; }
  86. #single h2 { font-size: 4em; }
  87. #single img { padding: 3px; border: 3px solid #999; }
  88. #single img:hover { border-color: #000; }
  89. '''
  90. tpl_index = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  91. <html>
  92. <head>
  93. <title>%(TITLE)s</title>
  94. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  95. <link rel="stylesheet" href="gallerygen.css" type="text/css" />
  96. <meta name="generator" content="gallerygen by MySZ (c) 2008" />
  97. </head>
  98. <body>
  99. %(BODY)s
  100. </body>
  101. </html>'''
  102. tpl_thumbs = '''
  103. <div id="header">
  104. <ul id="navi">
  105. <li id="navi_next">%(next)s</li>
  106. <li id="navi_top">%(top)s</li>
  107. <li id="navi_prev">%(prev)s</li>
  108. </ul>
  109. <br class="clear" />
  110. </div>
  111. <table id="thumbs">
  112. %(thumbs_rows)s
  113. </table>'''
  114. tpl_thumbs_row = '''
  115. <tr>
  116. %(thumbs_cols)s
  117. </tr>
  118. '''
  119. tpl_thumbs_col = '''
  120. <td>
  121. <a href="%(href_small)s"><img src="%(img_th)s" width="%(th_width)s" height="%(th_height)s" title="%(img_desc)s" /></a>
  122. <p>%(img_name)s</p>
  123. </td>
  124. '''
  125. tpl_single = '''
  126. <div id="header">
  127. <ul id="navi">
  128. <li id="navi_next">%(next)s</li>
  129. <li id="navi_top">%(top)s</li>
  130. <li id="navi_prev">%(prev)s</li>
  131. </ul>
  132. <br class="clear" />
  133. </div>
  134. <table id="single" width="%(img_width)s">
  135. <tr>
  136. <td colspan="2"><a href="%(href_big)s"><img src="%(img_small)s" width="%(img_width)s" height="%(img_height)s"></a></td>
  137. </tr>
  138. <tr>
  139. <td id="img_name"><h2>%(img_name)s</h2></td>
  140. <td id="img_desc">%(img_desc)s</td>
  141. </tr>
  142. </table>
  143. '''
  144. def thumb_create (images, width=100, height=100):
  145. ret = 0
  146. for src, dst in images:
  147. try:
  148. w, h = commands.getoutput ('identify ' + src).split ()[2].split ('x')
  149. w, h, l, t = int (w), int (h), 0, 0
  150. if w > h:
  151. l = round ((w - h) / 2)
  152. w = h
  153. elif h > w:
  154. t = round ((h - w) / 2)
  155. h = w
  156. commands.getoutput ('convert -crop %dx%d+%d+%d %s %s' % (w, h, l, t, src, dst))
  157. commands.getoutput ('convert -resize %dx%d %s %s' % (width, height, dst, dst))
  158. # commands.getoutput ('convert -blur 15 %s %s' % (dst, dst + '.jpg'))
  159. ret += 1
  160. except:
  161. try:
  162. if os.path.isfile (dst):
  163. os.remove (dst)
  164. except:
  165. pass
  166. return ret
  167. def image_resize (path, size='50%', sharpen=2):
  168. src, dst = path
  169. try:
  170. if sharpen:
  171. sharpen = '-sharpen ' + str (sharpen)
  172. else:
  173. sharpen = ''
  174. commands.getoutput ('convert -resize %s %s %s %s' % (size, sharpen, src, dst))
  175. except:
  176. try:
  177. if os.path.isfile (dst):
  178. os.remove (dst)
  179. except:
  180. pass
  181. return False
  182. return True
  183. def main ():
  184. try:
  185. cfg = Config ()
  186. cfg.load ('/Users/mysz/.gallerygen.rc')
  187. except Exception:
  188. print (e, file=sys.stderr)
  189. raise SystemExit (5)
  190. ## parsing parameters
  191. import getopt
  192. opt_short = 'vd:m:n:r:c:t:sk:l:f:o:p:a:'
  193. opt_long = ('version', 'directory=', 'width=', 'height=', 'rows=', 'cols=', 'title=', 'skip-images',
  194. 'single-width=', 'single-height=', 'config=', 'big-width=', 'big-height=', 'sharpen=', 'help'
  195. )
  196. try:
  197. opts, args = getopt.gnu_getopt (sys.argv[1:], opt_short, opt_long)
  198. except getopt.GetoptError, e:
  199. print (repr (e))
  200. raise SystemExit (1)
  201. try:
  202. for o, a in opts:
  203. if o in ('--help'):
  204. print (usage)
  205. raise SystemExit (0)
  206. elif o in ('-v', '--version'):
  207. print (version)
  208. raise SystemExit (0)
  209. elif o in ('-d', '--directory'):
  210. cfg.directory = a
  211. elif o in ('-m', '--width'):
  212. cfg.th_width = a
  213. elif o in ('-n', '--height'):
  214. cfg.th_height = a
  215. elif o in ('-r', '--rows'):
  216. cfg.th_rows = a
  217. elif o in ('-c', '--cols'):
  218. cfg.th_cols = a
  219. elif o in ('-s', '--skip-images'):
  220. cfg.skip_images = True
  221. elif o in ('-t', '--title'):
  222. cfg.title = a
  223. elif o in ('-k', '--single-width'):
  224. cfg.single_width = a
  225. elif o in ('-l', '--single-height'):
  226. cfg.single_height = a
  227. elif o in ('-o', '--big-width'):
  228. cfg.big_width = a
  229. elif o in ('-p', '--big-height'):
  230. cfg.big_height = a
  231. elif o in ('-f', '--config'):
  232. cfg.config = a
  233. elif o in ('-a', '--sharpen'):
  234. cfg.sharpen = a
  235. except Exception, e:
  236. print ('Error occured: %s' % repr (e), file=sys.stderr)
  237. raise SystemExit (2)
  238. ## validating
  239. if not cfg.directory:
  240. print ("Give me some directory!", file=sys.stderr)
  241. raise SystemExit (7)
  242. elif not os.path.isdir (cfg.directory):
  243. print ('"%s" is not a directory.' % cfg.directory, file=sys.stderr)
  244. raise SystemExit (3)
  245. try:
  246. cfg.th_width = int (cfg.th_width)
  247. cfg.th_height = int (cfg.th_height)
  248. cfg.th_rows = int (cfg.th_rows)
  249. cfg.th_cols = int (cfg.th_cols)
  250. cfg.single_width = int (cfg.single_width)
  251. cfg.single_height = int (cfg.single_height)
  252. cfg.big_width = int (cfg.big_width)
  253. cfg.big_height = int (cfg.big_height)
  254. except Exception, e:
  255. print ('Some error occured: "%s".' % e)
  256. raise SystemExit (6)
  257. ## search for local config (per album)
  258. if os.path.isfile (os.path.join (cfg.directory, 'gengallery.rc')):
  259. cfg.load (os.path.join (cfg.directory, 'gengallery.rc'))
  260. ## another config - specified with parameters
  261. if cfg.config:
  262. cfg.load (cfg.config)
  263. ## search for images, create thumbs and read descriptions
  264. files = list ()
  265. for o in os.listdir (cfg.directory):
  266. path = os.path.join (cfg.directory, o)
  267. if not cfg.images.search (o) or not os.path.isfile (path):
  268. continue
  269. path_small = os.path.splitext (path)
  270. path_small = '_small'.join (path_small)
  271. path_th = os.path.splitext (path)
  272. path_th = '_th'.join (path_th)
  273. if not cfg.skip_images:
  274. if cfg.big_width and cfg.big_height:
  275. image_resize ((path, path), '%dx%d' % (cfg.big_width, cfg.big_height))
  276. image_resize ((path, path_small), '%dx%d' % (cfg.single_width, cfg.single_height))
  277. thumb_create (((path, path_th), ), cfg.th_width, cfg.th_height)
  278. elif os.path.splitext (o)[0].endswith ('_small') or\
  279. os.path.splitext (o)[0].endswith ('_th'):
  280. continue
  281. desc = os.path.splitext (path)[0] + '.txt'
  282. if not os.path.isfile (desc):
  283. desc = ''
  284. else:
  285. with open (desc, 'r') as fh:
  286. desc = fh.read ()
  287. files.append ((o, os.path.basename (path_small), os.path.basename (path_th), desc))
  288. if not files:
  289. print ('Directory is empty.', file=sys.stderr)
  290. raise SystemExit (4)
  291. th_per_page = cfg.th_rows * cfg.th_cols
  292. pages = int (math.ceil (len (files) / float (th_per_page)))
  293. index = -1
  294. ## create css
  295. if cfg.css:
  296. with open (os.path.join (cfg.directory, 'gallerygen.css'), 'w') as fh:
  297. fh.write (cfg.css)
  298. ## iterate with pages
  299. for page in range (1, pages+1):
  300. with open (os.path.join (cfg.directory, 'page_%05d.html' % page), 'w') as fh:
  301. ## iterate with rows of thumbs
  302. rows = []
  303. for row in range (1, cfg.th_rows + 1):
  304. ## iterate with columns of thumbs
  305. cols = []
  306. for col in range (1, cfg.th_cols + 1):
  307. index += 1
  308. try:
  309. f = files[index]
  310. except IndexError:
  311. pass
  312. else:
  313. ## create single image page
  314. with open (os.path.join (cfg.directory, f[0] + '.html'), 'w') as fs:
  315. small_width = small_height = 0
  316. img_path = os.path.join (cfg.directory, f[1])
  317. if os.path.isfile (img_path):
  318. data = commands.getoutput ('identify ' + img_path)
  319. if data:
  320. small_width, small_height = data.split ()[2].split ('x')
  321. small_width = int (small_width)
  322. small_height = int (small_height)
  323. if not small_width or not small_height:
  324. small_width = cfg.single_width
  325. small_height = cfg.single_height
  326. print (cfg.tpl_index % dict (
  327. BODY = cfg.tpl_single % dict (
  328. top = '<a href="page_%05d.html">top</a>' % page,
  329. prev = '<a href="%s.html">prev</a>' % files[index-1][0] if index - 1 > 0 else 'prev',
  330. next = '<a href="%s.html">next</a>' % files[index+1][0] if index + 1 < len (files) else 'next',
  331. href_big = f[0],
  332. img_name = f[0],
  333. img_small = f[1],
  334. img_width = small_width,
  335. img_height = small_height,
  336. img_desc = f[3],
  337. ),
  338. TITLE = cfg.title % dict (
  339. date = time.strftime ('%Y-%m-%d', time.localtime ()),
  340. pageno = f[0],
  341. ),
  342. ), file=fs)
  343. ## parse tpl: single thumbnail
  344. cols.append (cfg.tpl_thumbs_col % dict (
  345. href_small = f[0] + '.html',
  346. img_th = f[2],
  347. img_name = f[0],
  348. img_desc = f[3],
  349. th_width = cfg.th_width,
  350. th_height = cfg.th_height,
  351. ))
  352. ## parse tpl: single row of thumbs
  353. rows.append (cfg.tpl_thumbs_row % dict (thumbs_cols = ''.join (cols)))
  354. ## parse tpl and create page
  355. print (cfg.tpl_index % dict (
  356. TITLE = cfg.title % dict (
  357. date = time.strftime ('%Y-%m-%d', time.localtime ()),
  358. pageno = page,
  359. ),
  360. BODY = cfg.tpl_thumbs % dict (
  361. top = page,
  362. prev = '<a href="page_%05d.html">prev (%d)</a>' % (page-1, page-1) if page > 1 else 'prev',
  363. next = '<a href="page_%05d.html">next (%d)</a>' % (page+1, page+1) if page < pages else 'next',
  364. thumbs_rows = ''.join (rows),
  365. title = cfg.title % dict (
  366. date = time.strftime ('%Y-%m-%d', time.localtime ()),
  367. pageno = page,
  368. ),
  369. ),
  370. ), file=fh)
  371. if __name__ == '__main__':
  372. main ()
  373. # vim: ft=python