浏览代码

initial release

Markus Spring 1 年之前
父节点
当前提交
9c4714e591
共有 2 个文件被更改,包括 516 次插入0 次删除
  1. 302 0
      create_lightgallery.py
  2. 214 0
      export_to_lightgallery.lua

+ 302 - 0
create_lightgallery.py

@@ -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}'>&gt;&gt;&nbsp;{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}&nbsp;&lt;&lt;</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='&nbsp;&nbsp;&nbsp;&nbsp;'.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:
+

+ 214 - 0
export_to_lightgallery.lua

@@ -0,0 +1,214 @@
+local dt = require "darktable"
+local du = require "lib/dtutils"
+
+du.check_min_api_version("7.0.0", "lightgallery_export_module")
+
+local gettext = dt.gettext.gettext
+local function _(msgid)
+    return gettext(msgid)
+end
+
+local script_data = {}
+
+script_data.metadata = {
+    name = "lightgallery_export_module",
+    purpose = _("Export selected files into a directory and call a python script to create a lightGallery"),
+    author = "Markus Spring",
+    help = "https://example.com/help"
+}
+
+-- local variables
+local help_url = "https://example.com/help"
+local du = require "lib/dtutils"
+local df = require "lib/dtutils.file"
+local dtsys = require "lib/dtutils.system"
+local debug = require "darktable.debug"
+
+local image_max_xy = 2500
+local jpg_quality_str = '92'
+local PS = dt.configuration.running_os == "windows" and "\\" or "/"
+
+script_data.destroy = nil -- function to destory the script
+script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
+script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
+
+-- Register preferences
+dt.preferences.register("lightgallery_export", "export_path", "string", "LightGallery Export Path", "Path to export lightGallery", "")
+dt.preferences.register("lightgallery_export", "gallery_title", "string", "LightGallery Title", "Title for the lightGallery", "--..--")
+dt.preferences.register("lightgallery_export", "gallery_uplink", "string", "LightGallery Uplink", "Uplink for lightGallery index.html", "..")
+dt.preferences.register("lightgallery_export", "gallery_family_tags", "bool", "LightGallery attach published..family tag", "attach published..family tag", false)
+dt.preferences.register("lightgallery_export", "exiftoolbinary", "file",
+                        _("lightGallery: executable for exiftool"), 
+                        _("select executable for exiftool command line version")  , "")
+dt.preferences.register("lightgallery_export", "post_export_script", "file", 
+                        _("lightGallery: select script to run after image export"), 
+                        _("select script to run after image export")  , "")
+
+function mws_basename(str)
+   local name = string.gsub(str, "(.*)%..*", "%1")
+   return name
+end
+
+function exists(name)
+   if type(name)~="string" then return false end
+   return os.rename(name,name) and true or false
+end
+
+function isFile(name)
+   if type(name)~="string" then return false end
+   if not exists(name) then return false end
+   local f = io.open(name)
+   if f then
+      f:close()
+      return true
+   end
+   return false
+end
+
+function isDir(name)
+   return (exists(name) and not isFile(name))
+end
+
+local export_path_widget = dt.new_widget("box") {
+    orientation = "horizontal",
+    dt.new_widget("label") {
+        label = "Verzeichnis: "
+    },
+    dt.new_widget("file_chooser_button") {
+       title = "Select export path",
+       is_directory = true,
+       tooltip = "Choose the directory where to export the images for the lightGallery",
+       value = dt.preferences.read("lightgallery_export", "export_path", "string")
+    }
+}
+
+local gallery_title_widget = dt.new_widget("box") {
+    orientation = "horizontal",
+    dt.new_widget("label") {
+        label = "Titel: "
+    },
+    dt.new_widget("entry") {
+       text = dt.preferences.read("lightgallery_export", "gallery_title", "string"),
+       tooltip = "Enter the title for the lightGallery"
+    }
+}
+
+local gallery_uplink_widget = dt.new_widget("box") {
+    orientation = "horizontal",
+    dt.new_widget("label") {
+        label = "Uplink: "
+    },
+    dt.new_widget("entry") {
+       text = dt.preferences.read("lightgallery_uplink", "gallery_uplink", "string"),
+       tooltip = "Uplink for the lightGallery index.html"
+    }
+}
+
+local gallery_family_tags_widget = dt.new_widget("check_button"){
+    label = "Attach photography|...|family Tag",
+    value = dt.preferences.read("lightgallery_export", "gallery_family_tags", "bool"),
+    tooltip = "Click to toggle the checkbox",
+    clicked_callback = function(self)
+       dt.preferences.write("lightgallery_export", "gallery_family_tags", "bool", self.value)
+    end
+}
+
+-- Function to save preferences
+local function save_preferences()
+    dt.preferences.write("lightgallery_export", "export_path", "string", export_path_widget.value)
+    dt.preferences.write("lightgallery_export", "gallery_title", "string", gallery_title_widget.text)
+    dt.preferences.write("lightgallery_export", "lightgallery_uplink", "string", gallery_uplink_widget.text)
+end
+
+-- Function to export images to Light Gallery
+local function export_to_lightgallery()
+    local exp_path = export_path_widget.value
+    local gallery_title = gallery_title_widget.text
+    local exiftool = dt.preferences.read("lightgallery_export", "exiftoolbinary", "file")
+
+    -- Save preferences
+    save_preferences()
+
+    -- Create the export directory if it doesn't exist
+    os.execute("mkdir -p " .. exp_path)
+
+    local jpeg_exporter      = dt.new_format("jpeg")
+    jpeg_exporter.quality = jpg_quality_str
+    jpeg_exporter.max_height = image_max_xy
+    jpeg_exporter.max_width  = image_max_xy
+    
+    -- Loop over the selected images
+    local images = dt.gui.selection()
+    for _, i in ipairs(images) do
+        local tmpname = os.tmpname()
+        local tmp_exported = tmpname..".jpg"
+        if dt.configuration.running_os == "windows" then
+           tmp_exported = dt.configuration.tmp_dir .. tmp_exported -- windows os.tmpname() defaults to root directory
+        end
+        if i.rating < 2 then
+           i.rating = 2
+        end
+        dt.print("Exporting "..i.filename)
+        jpeg_exporter:write_image(i, tmp_exported, false)
+        run_cmd = exiftool..' -TagsFromFile '..df.sanitize_filename(i.sidecar)..' -xmp:all -exif:all --subifd:all -overwrite_original '..df.sanitize_filename(tmp_exported)
+        dt.print_log(string.format("Running: %s", run_cmd))
+        resp = dtsys.external_command(run_cmd)
+        targetfile = df.sanitize_filename(string.gsub(exp_path, "'", "")..PS..mws_basename(i.filename)..'.jpg')
+        dt.print('mv ' .. tmp_exported .. ' ' .. targetfile)
+        os.execute('mv ' .. tmp_exported .. ' ' .. targetfile)
+        if dt.preferences.read("lightgallery_export", "gallery_family_tags", "bool") then
+           local tagnr = dt.tags.find('photography|published|gallery|family') --- tag must exist!
+           dt.tags.attach(tagnr,i)
+        end
+        os.execute( "rm -f " .. tmpname )        
+    end
+    local post_export_script = dt.preferences.read("lightgallery_export", "post_export_script", "file")
+    dt.print("post_export_script: ".. post_export_script.." "..exp_path)
+    os.execute( post_export_script.." "..exp_path ) 
+    -- tell the user that everything worked
+    dt.print("Exported " .. #images .. " images to lightGallery")
+end
+
+-- Create export button
+local gallery_export_button = dt.new_widget("button"){
+    label = "Run Export",
+    clicked_callback = export_to_lightgallery
+}
+
+-- Create a button widget with a help link
+local help_button = dt.new_widget("button"){
+    label = "Help: "..help_url,
+    clicked_callback = function()
+        -- Open the help URL in the default web browser
+        dt.control.execute("xdg-open "..help_url)
+    end
+}
+
+-- Create the module widget
+local module_widget = dt.new_widget("box"){
+    orientation = "vertical",
+    gallery_title_widget,
+    export_path_widget,
+    gallery_uplink_widget,
+    gallery_family_tags_widget,
+    gallery_export_button,
+    help_button
+}
+
+-- Register the module
+dt.register_lib(
+    "lightgallery_export_module",
+    "lightGallery Export",
+    true,
+    false,
+    {
+        [dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100},
+    },
+    module_widget
+)
+
+script_data.destroy = nil
+script_data.destroy_method = nil
+
+return script_data
+