create_lightgallery.py 12 KB

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