create_lightgallery.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. #!/usr/bin/python3
  2. import os
  3. from os.path import isdir, isfile, join, getmtime
  4. from pathlib import Path
  5. import pyexiv2
  6. import random
  7. import re
  8. import sys
  9. from string import Template
  10. import subprocess
  11. import traceback
  12. from configparser import ConfigParser
  13. from optparse import OptionParser
  14. from PIL import Image, ImageOps
  15. __all__ = []
  16. __version__ = 0.1
  17. __date__ = '2021-12-27'
  18. __updated__ = '2021-12-27'
  19. DEBUG = 0
  20. TESTRUN = 0
  21. PROFILE = 0
  22. thumbsize = (202, 202)
  23. thumbsize = (250, 250)
  24. html = Template("""<html>
  25. <head>
  26. <title>$title</title>
  27. <link type="text/css" rel="stylesheet" href="/res/css/lightgallery-bundle.css" />
  28. <link type="text/css" rel="stylesheet" href="/res/css/lg-zoom.css" />
  29. <link type="text/css" rel="stylesheet" href="/res/css/lg-thumbnail.css" />
  30. <link type="text/css" rel="stylesheet" href="/res/css/bootstrap.min.css" />
  31. <link type="text/css" rel="stylesheet" href="/res/user.css" />
  32. <script src="/res/js/lightgallery.min.js"></script>
  33. <script src="/res/plugins/hash/lg-hash.min.js"></script>
  34. <script src="/res/plugins/fullscreen/lg-fullscreen.min.js"></script>
  35. <script src="/res/plugins/autoplay/lg-autoplay.min.js"></script>
  36. <script src="/res/plugins/thumbnail/lg-thumbnail.min.js"></script>
  37. <script src="/res/plugins/zoom/lg-zoom.min.js"></script>
  38. <style>
  39. #header {
  40. background-image: url("header/header.jpg");
  41. }
  42. </style>
  43. </head>
  44. <body>
  45. <div id="header">
  46. <h1>$title</h1>
  47. <div id="nav">$navigation</div>
  48. </div>
  49. <div id="outer_lightgallery">
  50. <div id="lightgallery">
  51. $images
  52. </div>
  53. </div>
  54. <script type="text/javascript">
  55. lightGallery(document.getElementById('lightgallery'), {
  56. plugins: [lgZoom, lgThumbnail, lgAutoplay, lgFullscreen ],
  57. //speed: 500,
  58. //mode: 'fade',
  59. licenseKey: '0000-0000-000-0000'
  60. });
  61. </script>
  62. </body>
  63. </html>
  64. """)
  65. # Priorität der Auswertung
  66. # 1. Exifdaten
  67. def get_caption(jpg):
  68. metadata = pyexiv2.ImageMetadata(jpg)
  69. metadata.read()
  70. tags = ''
  71. indextags = ''
  72. year = ''
  73. try:
  74. year = re.sub(':\d\d:\d\d\s\d\d:\d\d:\d\d', '', metadata['Exif.Image.DateTimeOriginal'].raw_value)
  75. tags = str(year)
  76. indextags = 'year|' + year
  77. except:
  78. pass
  79. try:
  80. tags = tags + ', ' + metadata['Xmp.dc.title'].value["x-default"]
  81. indextags = indextags + ',' + 'title|' + metadata['Xmp.dc.title'].value["x-default"].replace(',','')
  82. except:
  83. pass
  84. try:
  85. tags = tags + ', ' + metadata['Xmp.dc.description'].value["x-default"]
  86. indextags = indextags + ',' + 'description|' + metadata['Xmp.dc.description'].value["x-default"].replace(',','')
  87. except:
  88. pass
  89. try:
  90. for l in metadata['Xmp.lr.hierarchicalSubject'].raw_value:
  91. tags = tags + ', ' + l.split('|')[-1] # last element of list
  92. indextags = indextags + ',' + l.split('|')[0] + '|' + l.split('|')[-1] # last element of list
  93. except:
  94. pass
  95. return(re.sub('^,\s', '', tags), re.sub('^,\s*', '', indextags), )
  96. def create_header(jpeglist):
  97. header = random.choice(jpeglist)
  98. if not os.path.exists('header'):
  99. os.makedirs('header')
  100. xsize = '2000'
  101. cmd = ['convert', '-quiet', header, '-thumbnail', xsize + 'x400^', '-gravity', 'center', '-extent', xsize + 'x400', "header/header.jpg"]
  102. subprocess.call(cmd, shell=False)
  103. def getimagesize(jpg):
  104. jpg = str(jpg)
  105. im = Image.open(jpg)
  106. width, height = im.size
  107. return(f"data-lg-size=\"{width}-{height}\"")
  108. def create_nav_link(link, text):
  109. if link == None:
  110. return('')
  111. link = str(link)
  112. if link.find(' ') > -1:
  113. link, text = link.split(' ', 1)
  114. return(f"<a href='{link}'>{text}</a>")
  115. def create_forward_link(link, text):
  116. if link == None:
  117. return('')
  118. link = str(link)
  119. if link.find(' ') > -1:
  120. link, text = link.split(' ', 1)
  121. return(f"<a href='{link}'>&gt;&gt;&nbsp;{text}</a>")
  122. def create_backward_link(link, text):
  123. if link == None:
  124. return('')
  125. link = str(link)
  126. if link.find(' ') > -1:
  127. link, text = link.split(' ', 1)
  128. return(f"<a href='{link}'>{text}&nbsp;&lt;&lt;</a>")
  129. def read_title_file():
  130. """ Wenn im aktuellen Verzeichnis eine Datei _title existiert, gib ihren Inhalt zurück"""
  131. try:
  132. with open('_title', 'r') as titlefile:
  133. return(titlefile.read())
  134. except:
  135. return None
  136. def makethumb(jpg):
  137. if not os.path.isdir('_thumbnails'):
  138. os.mkdir('_thumbnails');
  139. if not os.path.isfile(join('_thumbnails', jpg)):
  140. image = Image.open(jpg)
  141. # thumb = ImageOps.fit(image, thumbsize, Image.ANTIALIAS)
  142. # thumb.save(join('_thumbnails', jpg))
  143. image.thumbnail(thumbsize, Image.Resampling.LANCZOS)
  144. image.save(join('_thumbnails', jpg))
  145. else:
  146. if getmtime(jpg) > getmtime(join('_thumbnails', jpg)):
  147. image = Image.open(jpg)
  148. # thumb = ImageOps.fit(image, thumbsize, Image.ANTIALIAS)
  149. # thumb.save(join('_thumbnails', jpg))
  150. image.thumbnail(thumbsize, Image.ANTIALIAS)
  151. image.save(join('_thumbnails', jpg))
  152. def read_config(opts):
  153. try:
  154. config_object = ConfigParser()
  155. config_object.read( "." + os.path.splitext(os.path.basename(__file__))[0] )
  156. ini = dict(config_object.items('default'))
  157. for key in ini:
  158. if not opts.__dict__[key]:
  159. opts.__dict__[key] = ini[key]
  160. except:
  161. pass
  162. def write_config(opts):
  163. ini = {}
  164. for key in opts.__dict__:
  165. value = opts.__dict__[key]
  166. if value is None:
  167. pass
  168. elif key == 'verbose':
  169. pass
  170. else:
  171. ini[key] = str(value)
  172. config_object = ConfigParser()
  173. config_object.read_dict({'default' : ini})
  174. with open("." + os.path.splitext(os.path.basename(__file__))[0], 'w') as conf:
  175. config_object.write(conf)
  176. def create_index_html(indexdir, opts):
  177. startdir = os.getcwd()
  178. os.chdir(indexdir)
  179. read_config(opts)
  180. jpegs = []
  181. jpeglist = []
  182. valid_images = [".jpg",".JPG"]
  183. dirlist = os.listdir('.')
  184. dirlist.sort()
  185. for jpg in dirlist:
  186. ext = os.path.splitext(jpg)[1]
  187. if ext.lower() not in valid_images:
  188. continue
  189. makethumb(jpg);
  190. jpg = str(jpg)
  191. jpeglist.append(jpg)
  192. thumb = jpg
  193. caption, indextags = get_caption(jpg)
  194. sizestring = getimagesize(jpg)
  195. jpegs.append(f""" <a href="{jpg}" {sizestring} data-sub-html="{caption}" data_index="{indextags}">
  196. <img src="_thumbnails/{thumb}" />
  197. </a>""")
  198. images = "\n".join(jpegs)
  199. create_header(jpeglist)
  200. forward = create_forward_link(opts.forward, 'weiter')
  201. backward = create_backward_link(opts.backward, 'zurück')
  202. up = create_nav_link('..', 'Index')
  203. title = read_title_file() or opts.title or os.path.basename(os.path.normpath(os.getcwd()))
  204. with open('index.html', 'w') as f:
  205. f.write(html.substitute(images=images, title=title, navigation='&nbsp;&nbsp;&nbsp;&nbsp;'.join([backward, up, forward]) ))
  206. write_config(opts)
  207. os.chdir(startdir)
  208. def main(argv=None):
  209. '''Command line options.'''
  210. program_name = os.path.basename(sys.argv[0])
  211. program_version = "v0.1"
  212. program_build_date = "%s" % __updated__
  213. program_version_string = '%%prog %s (%s)' % (program_version, program_build_date)
  214. program_longdesc = '''create index.html for lightgallery in a directory of jpegs'''
  215. program_license = "Copyright 2021 Markus Spring (markus-spring.info). \
  216. Licensed under the Apache License 2.0\nhttp://www.apache.org/licenses/LICENSE-2.0"
  217. if argv is None:
  218. argv = sys.argv[1:]
  219. try:
  220. # setup option parser
  221. parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license)
  222. # parser.add_option("-i", "--in", dest="infile", help="set input path [default: %default]", metavar="FILE")
  223. # parser.add_option("-o", "--out", dest="outfile", help="set output path [default: %default]", metavar="FILE")
  224. parser.add_option("-v", "--verbose", dest="verbose", action="count", help="set verbosity level [default: %default]")
  225. parser.add_option("-t", "--title", action="store", dest="title", help="set gallery title [default: %default]")
  226. parser.add_option("-f", "--forward", action="store", dest="forward", help="forward url")
  227. parser.add_option("-b", "--backward", action="store", dest="backward", help="backward url")
  228. parser.add_option("-u", "--up", action="store", dest="up", help="up url")
  229. parser.set_defaults( verbose=0 )
  230. (opts, args) = parser.parse_args(argv)
  231. if opts.verbose > 0:
  232. print(("verbosity level = %d" % opts.verbose))
  233. # if opts.title:
  234. # print(("title = %s" % opts.title))
  235. if len(args) < 1:
  236. parser.error("directory argument missing")
  237. # MAIN BODY #
  238. create_index_html(args[0], opts)
  239. except BaseException as ex:
  240. ex_type, ex_value, ex_traceback = sys.exc_info() # Get current system exception
  241. trace_back = traceback.extract_tb(ex_traceback) # Extract unformatter stack traces as tuples
  242. stack_trace = list() # Format stacktrace
  243. for trace in trace_back:
  244. stack_trace.append("File : %s , Line : %d, Func.Name : %s, Message : %s" % (trace[0], trace[1], trace[2], trace[3]))
  245. print("Exception type : %s " % ex_type.__name__)
  246. print("Exception message : %s" %ex_value)
  247. print("Stack trace : %s" %stack_trace)
  248. if __name__ == "__main__":
  249. if DEBUG:
  250. sys.argv.append("-h")
  251. if TESTRUN:
  252. import doctest
  253. doctest.testmod()
  254. if PROFILE:
  255. import cProfile
  256. import pstats
  257. profile_filename = 'config_reader.config_reader_profile.txt'
  258. cProfile.run('main()', profile_filename)
  259. statsfile = open("profile_stats.txt", "wb")
  260. p = pstats.Stats(profile_filename, stream=statsfile)
  261. stats = p.strip_dirs().sort_stats('cumulative')
  262. stats.print_stats()
  263. statsfile.close()
  264. sys.exit(0)
  265. sys.exit(main())
  266. # Local Variables:
  267. # compile-command: "python3 /home/springm/projekte/python/lightgallery/create_lightgallery.py -t '1958-1963' -f '../002_1960-1962 1960-1962' -u.. /media/vaters_dias/001_1958-1963"
  268. # End: