|
@@ -0,0 +1,302 @@
|
|
|
|
|
+#!/usr/bin/python3
|
|
|
|
|
+
|
|
|
|
|
+import os
|
|
|
|
|
+from os.path import isdir, isfile, join, getmtime
|
|
|
|
|
+from pathlib import Path
|
|
|
|
|
+import pyexiv2
|
|
|
|
|
+import random
|
|
|
|
|
+import re
|
|
|
|
|
+import sys
|
|
|
|
|
+from string import Template
|
|
|
|
|
+import subprocess
|
|
|
|
|
+import traceback
|
|
|
|
|
+from configparser import ConfigParser
|
|
|
|
|
+from optparse import OptionParser
|
|
|
|
|
+from PIL import Image, ImageOps
|
|
|
|
|
+
|
|
|
|
|
+__all__ = []
|
|
|
|
|
+__version__ = 0.1
|
|
|
|
|
+__date__ = '2021-12-27'
|
|
|
|
|
+__updated__ = '2021-12-27'
|
|
|
|
|
+
|
|
|
|
|
+DEBUG = 0
|
|
|
|
|
+TESTRUN = 0
|
|
|
|
|
+PROFILE = 0
|
|
|
|
|
+
|
|
|
|
|
+thumbsize = (202, 202)
|
|
|
|
|
+thumbsize = (250, 250)
|
|
|
|
|
+
|
|
|
|
|
+html = Template("""<html>
|
|
|
|
|
+ <head>
|
|
|
|
|
+ <title>$title</title>
|
|
|
|
|
+ <link type="text/css" rel="stylesheet" href="/res/css/lightgallery-bundle.css" />
|
|
|
|
|
+ <link type="text/css" rel="stylesheet" href="/res/css/lg-zoom.css" />
|
|
|
|
|
+ <link type="text/css" rel="stylesheet" href="/res/css/lg-thumbnail.css" />
|
|
|
|
|
+ <link type="text/css" rel="stylesheet" href="/res/css/bootstrap.min.css" />
|
|
|
|
|
+ <link type="text/css" rel="stylesheet" href="/res/user.css" />
|
|
|
|
|
+ <script src="/res/js/lightgallery.min.js"></script>
|
|
|
|
|
+ <script src="/res/plugins/hash/lg-hash.min.js"></script>
|
|
|
|
|
+ <script src="/res/plugins/fullscreen/lg-fullscreen.min.js"></script>
|
|
|
|
|
+ <script src="/res/plugins/autoplay/lg-autoplay.min.js"></script>
|
|
|
|
|
+ <script src="/res/plugins/thumbnail/lg-thumbnail.min.js"></script>
|
|
|
|
|
+ <script src="/res/plugins/zoom/lg-zoom.min.js"></script>
|
|
|
|
|
+ <style>
|
|
|
|
|
+ #header {
|
|
|
|
|
+ background-image: url("header/header.jpg");
|
|
|
|
|
+ }
|
|
|
|
|
+ </style>
|
|
|
|
|
+ </head>
|
|
|
|
|
+ <body>
|
|
|
|
|
+ <div id="header">
|
|
|
|
|
+ <h1>$title</h1>
|
|
|
|
|
+ <div id="nav">$navigation</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div id="outer_lightgallery">
|
|
|
|
|
+ <div id="lightgallery">
|
|
|
|
|
+ $images
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <script type="text/javascript">
|
|
|
|
|
+ lightGallery(document.getElementById('lightgallery'), {
|
|
|
|
|
+ plugins: [lgZoom, lgThumbnail, lgAutoplay, lgFullscreen ],
|
|
|
|
|
+ //speed: 500,
|
|
|
|
|
+ //mode: 'fade',
|
|
|
|
|
+ licenseKey: '0000-0000-000-0000'
|
|
|
|
|
+ });
|
|
|
|
|
+ </script>
|
|
|
|
|
+ </body>
|
|
|
|
|
+</html>
|
|
|
|
|
+""")
|
|
|
|
|
+
|
|
|
|
|
+# Priorität der Auswertung
|
|
|
|
|
+# 1. Exifdaten
|
|
|
|
|
+def get_caption(jpg):
|
|
|
|
|
+ metadata = pyexiv2.ImageMetadata(jpg)
|
|
|
|
|
+ metadata.read()
|
|
|
|
|
+ tags = ''
|
|
|
|
|
+ indextags = ''
|
|
|
|
|
+ year = ''
|
|
|
|
|
+ try:
|
|
|
|
|
+ year = re.sub(':\d\d:\d\d\s\d\d:\d\d:\d\d', '', metadata['Exif.Image.DateTimeOriginal'].raw_value)
|
|
|
|
|
+ tags = str(year)
|
|
|
|
|
+ indextags = 'year|' + year
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass
|
|
|
|
|
+ try:
|
|
|
|
|
+ tags = tags + ', ' + metadata['Xmp.dc.title'].value["x-default"]
|
|
|
|
|
+ indextags = indextags + ',' + 'title|' + metadata['Xmp.dc.title'].value["x-default"].replace(',','')
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass
|
|
|
|
|
+ try:
|
|
|
|
|
+ tags = tags + ', ' + metadata['Xmp.dc.description'].value["x-default"]
|
|
|
|
|
+ indextags = indextags + ',' + 'description|' + metadata['Xmp.dc.description'].value["x-default"].replace(',','')
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass
|
|
|
|
|
+ try:
|
|
|
|
|
+ for l in metadata['Xmp.lr.hierarchicalSubject'].raw_value:
|
|
|
|
|
+ tags = tags + ', ' + l.split('|')[-1] # last element of list
|
|
|
|
|
+ indextags = indextags + ',' + l.split('|')[0] + '|' + l.split('|')[-1] # last element of list
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass
|
|
|
|
|
+ return(re.sub('^,\s', '', tags), re.sub('^,\s*', '', indextags), )
|
|
|
|
|
+
|
|
|
|
|
+def create_header(jpeglist):
|
|
|
|
|
+ header = random.choice(jpeglist)
|
|
|
|
|
+ if not os.path.exists('header'):
|
|
|
|
|
+ os.makedirs('header')
|
|
|
|
|
+ xsize = '2000'
|
|
|
|
|
+ cmd = ['convert', '-quiet', header, '-thumbnail', xsize + 'x400^', '-gravity', 'center', '-extent', xsize + 'x400', "header/header.jpg"]
|
|
|
|
|
+ subprocess.call(cmd, shell=False)
|
|
|
|
|
+
|
|
|
|
|
+def getimagesize(jpg):
|
|
|
|
|
+ jpg = str(jpg)
|
|
|
|
|
+ im = Image.open(jpg)
|
|
|
|
|
+ width, height = im.size
|
|
|
|
|
+ return(f"data-lg-size=\"{width}-{height}\"")
|
|
|
|
|
+
|
|
|
|
|
+def create_nav_link(link, text):
|
|
|
|
|
+ if link == None:
|
|
|
|
|
+ return('')
|
|
|
|
|
+ link = str(link)
|
|
|
|
|
+ if link.find(' ') > -1:
|
|
|
|
|
+ link, text = link.split(' ', 1)
|
|
|
|
|
+ return(f"<a href='{link}'>{text}</a>")
|
|
|
|
|
+
|
|
|
|
|
+def create_forward_link(link, text):
|
|
|
|
|
+ if link == None:
|
|
|
|
|
+ return('')
|
|
|
|
|
+ link = str(link)
|
|
|
|
|
+ if link.find(' ') > -1:
|
|
|
|
|
+ link, text = link.split(' ', 1)
|
|
|
|
|
+ return(f"<a href='{link}'>>> {text}</a>")
|
|
|
|
|
+
|
|
|
|
|
+def create_backward_link(link, text):
|
|
|
|
|
+ if link == None:
|
|
|
|
|
+ return('')
|
|
|
|
|
+ link = str(link)
|
|
|
|
|
+ if link.find(' ') > -1:
|
|
|
|
|
+ link, text = link.split(' ', 1)
|
|
|
|
|
+ return(f"<a href='{link}'>{text} <<</a>")
|
|
|
|
|
+
|
|
|
|
|
+def read_title_file():
|
|
|
|
|
+ """ Wenn im aktuellen Verzeichnis eine Datei _title existiert, gib ihren Inhalt zurück"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ with open('_title', 'r') as titlefile:
|
|
|
|
|
+ return(titlefile.read())
|
|
|
|
|
+ except:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+def makethumb(jpg):
|
|
|
|
|
+ if not os.path.isdir('_thumbnails'):
|
|
|
|
|
+ os.mkdir('_thumbnails');
|
|
|
|
|
+ if not os.path.isfile(join('_thumbnails', jpg)):
|
|
|
|
|
+ image = Image.open(jpg)
|
|
|
|
|
+ # thumb = ImageOps.fit(image, thumbsize, Image.ANTIALIAS)
|
|
|
|
|
+ # thumb.save(join('_thumbnails', jpg))
|
|
|
|
|
+ image.thumbnail(thumbsize, Image.Resampling.LANCZOS)
|
|
|
|
|
+ image.save(join('_thumbnails', jpg))
|
|
|
|
|
+ else:
|
|
|
|
|
+ if getmtime(jpg) > getmtime(join('_thumbnails', jpg)):
|
|
|
|
|
+ image = Image.open(jpg)
|
|
|
|
|
+ # thumb = ImageOps.fit(image, thumbsize, Image.ANTIALIAS)
|
|
|
|
|
+ # thumb.save(join('_thumbnails', jpg))
|
|
|
|
|
+ image.thumbnail(thumbsize, Image.ANTIALIAS)
|
|
|
|
|
+ image.save(join('_thumbnails', jpg))
|
|
|
|
|
+
|
|
|
|
|
+def read_config(opts):
|
|
|
|
|
+ try:
|
|
|
|
|
+ config_object = ConfigParser()
|
|
|
|
|
+ config_object.read( "." + os.path.splitext(os.path.basename(__file__))[0] )
|
|
|
|
|
+ ini = dict(config_object.items('default'))
|
|
|
|
|
+ for key in ini:
|
|
|
|
|
+ if not opts.__dict__[key]:
|
|
|
|
|
+ opts.__dict__[key] = ini[key]
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+def write_config(opts):
|
|
|
|
|
+ ini = {}
|
|
|
|
|
+ for key in opts.__dict__:
|
|
|
|
|
+ value = opts.__dict__[key]
|
|
|
|
|
+ if value is None:
|
|
|
|
|
+ pass
|
|
|
|
|
+ elif key == 'verbose':
|
|
|
|
|
+ pass
|
|
|
|
|
+ else:
|
|
|
|
|
+ ini[key] = str(value)
|
|
|
|
|
+
|
|
|
|
|
+ config_object = ConfigParser()
|
|
|
|
|
+ config_object.read_dict({'default' : ini})
|
|
|
|
|
+ with open("." + os.path.splitext(os.path.basename(__file__))[0], 'w') as conf:
|
|
|
|
|
+ config_object.write(conf)
|
|
|
|
|
+
|
|
|
|
|
+def create_index_html(indexdir, opts):
|
|
|
|
|
+ startdir = os.getcwd()
|
|
|
|
|
+ os.chdir(indexdir)
|
|
|
|
|
+ read_config(opts)
|
|
|
|
|
+
|
|
|
|
|
+ jpegs = []
|
|
|
|
|
+ jpeglist = []
|
|
|
|
|
+ valid_images = [".jpg",".JPG"]
|
|
|
|
|
+ dirlist = os.listdir('.')
|
|
|
|
|
+ dirlist.sort()
|
|
|
|
|
+ for jpg in dirlist:
|
|
|
|
|
+ ext = os.path.splitext(jpg)[1]
|
|
|
|
|
+ if ext.lower() not in valid_images:
|
|
|
|
|
+ continue
|
|
|
|
|
+ makethumb(jpg);
|
|
|
|
|
+ jpg = str(jpg)
|
|
|
|
|
+ jpeglist.append(jpg)
|
|
|
|
|
+ thumb = jpg
|
|
|
|
|
+ caption, indextags = get_caption(jpg)
|
|
|
|
|
+ sizestring = getimagesize(jpg)
|
|
|
|
|
+ jpegs.append(f""" <a href="{jpg}" {sizestring} data-sub-html="{caption}" data_index="{indextags}">
|
|
|
|
|
+ <img src="_thumbnails/{thumb}" />
|
|
|
|
|
+ </a>""")
|
|
|
|
|
+ images = "\n".join(jpegs)
|
|
|
|
|
+
|
|
|
|
|
+ create_header(jpeglist)
|
|
|
|
|
+ forward = create_forward_link(opts.forward, 'weiter')
|
|
|
|
|
+ backward = create_backward_link(opts.backward, 'zurück')
|
|
|
|
|
+ up = create_nav_link('..', 'Index')
|
|
|
|
|
+
|
|
|
|
|
+ title = read_title_file() or opts.title or os.path.basename(os.path.normpath(os.getcwd()))
|
|
|
|
|
+ with open('index.html', 'w') as f:
|
|
|
|
|
+ f.write(html.substitute(images=images, title=title, navigation=' '.join([backward, up, forward]) ))
|
|
|
|
|
+ write_config(opts)
|
|
|
|
|
+ os.chdir(startdir)
|
|
|
|
|
+
|
|
|
|
|
+def main(argv=None):
|
|
|
|
|
+ '''Command line options.'''
|
|
|
|
|
+
|
|
|
|
|
+ program_name = os.path.basename(sys.argv[0])
|
|
|
|
|
+ program_version = "v0.1"
|
|
|
|
|
+ program_build_date = "%s" % __updated__
|
|
|
|
|
+
|
|
|
|
|
+ program_version_string = '%%prog %s (%s)' % (program_version, program_build_date)
|
|
|
|
|
+ program_longdesc = '''create index.html for lightgallery in a directory of jpegs'''
|
|
|
|
|
+ program_license = "Copyright 2021 Markus Spring (markus-spring.info). \
|
|
|
|
|
+ Licensed under the Apache License 2.0\nhttp://www.apache.org/licenses/LICENSE-2.0"
|
|
|
|
|
+
|
|
|
|
|
+ if argv is None:
|
|
|
|
|
+ argv = sys.argv[1:]
|
|
|
|
|
+ try:
|
|
|
|
|
+ # setup option parser
|
|
|
|
|
+ parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license)
|
|
|
|
|
+ # parser.add_option("-i", "--in", dest="infile", help="set input path [default: %default]", metavar="FILE")
|
|
|
|
|
+ # parser.add_option("-o", "--out", dest="outfile", help="set output path [default: %default]", metavar="FILE")
|
|
|
|
|
+ parser.add_option("-v", "--verbose", dest="verbose", action="count", help="set verbosity level [default: %default]")
|
|
|
|
|
+ parser.add_option("-t", "--title", action="store", dest="title", help="set gallery title [default: %default]")
|
|
|
|
|
+ parser.add_option("-f", "--forward", action="store", dest="forward", help="forward url")
|
|
|
|
|
+ parser.add_option("-b", "--backward", action="store", dest="backward", help="backward url")
|
|
|
|
|
+ parser.add_option("-u", "--up", action="store", dest="up", help="up url")
|
|
|
|
|
+
|
|
|
|
|
+ parser.set_defaults( verbose=0 )
|
|
|
|
|
+
|
|
|
|
|
+ (opts, args) = parser.parse_args(argv)
|
|
|
|
|
+
|
|
|
|
|
+ if opts.verbose > 0:
|
|
|
|
|
+ print(("verbosity level = %d" % opts.verbose))
|
|
|
|
|
+ # if opts.title:
|
|
|
|
|
+ # print(("title = %s" % opts.title))
|
|
|
|
|
+
|
|
|
|
|
+ if len(args) < 1:
|
|
|
|
|
+ parser.error("directory argument missing")
|
|
|
|
|
+ # MAIN BODY #
|
|
|
|
|
+ create_index_html(args[0], opts)
|
|
|
|
|
+
|
|
|
|
|
+ except BaseException as ex:
|
|
|
|
|
+ ex_type, ex_value, ex_traceback = sys.exc_info() # Get current system exception
|
|
|
|
|
+ trace_back = traceback.extract_tb(ex_traceback) # Extract unformatter stack traces as tuples
|
|
|
|
|
+ stack_trace = list() # Format stacktrace
|
|
|
|
|
+
|
|
|
|
|
+ for trace in trace_back:
|
|
|
|
|
+ stack_trace.append("File : %s , Line : %d, Func.Name : %s, Message : %s" % (trace[0], trace[1], trace[2], trace[3]))
|
|
|
|
|
+ print("Exception type : %s " % ex_type.__name__)
|
|
|
|
|
+ print("Exception message : %s" %ex_value)
|
|
|
|
|
+ print("Stack trace : %s" %stack_trace)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+if __name__ == "__main__":
|
|
|
|
|
+ if DEBUG:
|
|
|
|
|
+ sys.argv.append("-h")
|
|
|
|
|
+ if TESTRUN:
|
|
|
|
|
+ import doctest
|
|
|
|
|
+ doctest.testmod()
|
|
|
|
|
+ if PROFILE:
|
|
|
|
|
+ import cProfile
|
|
|
|
|
+ import pstats
|
|
|
|
|
+ profile_filename = 'config_reader.config_reader_profile.txt'
|
|
|
|
|
+ cProfile.run('main()', profile_filename)
|
|
|
|
|
+ statsfile = open("profile_stats.txt", "wb")
|
|
|
|
|
+ p = pstats.Stats(profile_filename, stream=statsfile)
|
|
|
|
|
+ stats = p.strip_dirs().sort_stats('cumulative')
|
|
|
|
|
+ stats.print_stats()
|
|
|
|
|
+ statsfile.close()
|
|
|
|
|
+ sys.exit(0)
|
|
|
|
|
+ sys.exit(main())
|
|
|
|
|
+
|
|
|
|
|
+# Local Variables:
|
|
|
|
|
+# 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"
|
|
|
|
|
+# End:
|
|
|
|
|
+
|