commit 01904e9911dfef9f8f3aabbf749c071f0465537c
parent 61e1ad6c3a4b55c927892102df5ddc8cec9a4219
Author: ctucx <c@ctu.cx>
Date: Sun, 24 May 2020 18:44:49 +0200
parent 61e1ad6c3a4b55c927892102df5ddc8cec9a4219
Author: ctucx <c@ctu.cx>
Date: Sun, 24 May 2020 18:44:49 +0200
support for file-based configuration
5 files changed, 225 insertions(+), 112 deletions(-)
M
|
78
+++++++++++++++++++++++++++++++++++++++++++++---------------------------------
diff --git a/README.md b/README.md @@ -1,6 +1,5 @@ -# How to ctucx picture_gallery - -a little Example for https://git.ctu.cx/ctucx/gallery/ +# ctucx' gallery +a little introduction into this software. ## Install ```shell= @@ -11,11 +10,11 @@ a little Example for https://git.ctu.cx/ctucx/gallery/ ## Use the Programm ```shell= -[user@pc ~]$ ./gallery <source_dir> <target_dir> +[user@pc ~]$ ./gallery <config.file> ``` ## Dependencies Dependencies: ImageMagick, nim >= 1.0.6, nimble ## Infos -When running the program, a source folder and a destination folder must be specified, then the program creates all necessary HTML and CSS objects to display the pages correctly and creates images in the following sizes: medium: 1920x1080, thumbnail: 200x200 +When running the program, a config file must be specified, then the program creates all necessary HTML and CSS objects to display the pages correctly and creates images in the following sizes: medium: 1920x1080, thumbnail: 200x200
diff --git a/sample.config b/sample.config @@ -0,0 +1,18 @@ +SourceDir=/home/ctucx/Pictures/Bahnbilder +TargetDir=./out + +[Site] +Author=ctucx +Name="ctucx' sample gallery" +Description="a short discription for your site" +Tags="a list of tags for seo stuff" +ShowOriginalsButton=false +SymlinkOriginals=false +EnableJS=true ;if disabled no exif data will get parsed and keyboard navigation does not work + +[Thumbnail] +MediumMaxWidth=1920 +MediumMaxHeight=1080 +ThumbMaxWidth=200 +ThumbMaxHeight=200 +ThumbQuality=90
diff --git a/src/assets/album.html b/src/assets/album.html @@ -2,17 +2,22 @@ <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> - <title>ctucx' pics</title> - <link type="text/css" rel="stylesheet" href="/style.css"> - <link rel="shortcut icon" href="/favicon.ico"> + <title>{{name}} | {{SiteName}}</title> + <meta name="description" content="{{SiteDescription}}"> + <meta name="keywords" content="{{SiteTags}}"> + <meta name="author" content="{{SiteAuthor}}"> + + <meta name="generator" content="https://git.ctu.cx/ctucx/gallery"> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1.0, maximum-scale=1.0"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-capable" content="yes"> + + <link type="text/css" rel="stylesheet" href="/style.css"> + <link rel="shortcut icon" href="/favicon.ico"> </head> <body> - <header class="header"> {{#isSubalbum}} <a class="button" title="back" href=".."> @@ -110,22 +115,25 @@ {{#pictures}} <a href="{{name}}/" class="photo"> - <img src="./thumbnails/{{name}}.png" alt="Photo thumbnail" width="200" height="200"> + <img src="./thumbnails/{{name}}.png" alt="Photo thumbnail" width="{{ThumbThumbMaxWidth}}" height="{{ThumbThumbMaxHeight}}"> <span class="overlay"> <h1>{{name}}</h1> - <p><span title="Camera Date"><svg class="iconic "><use xlink:href="/iconic.svg#camera-slr"></use></svg></span></p> + <!--<p><span title="Camera Date"><svg class="iconic "><use xlink:href="/iconic.svg#camera-slr"></use></svg></span></p>--> </span> </a> {{/pictures}} </div> + {{#isSubalbum}} + {{#SiteEnableJS}} + <script type="text/javascript"> + window.onkeyup = function(e) { + if (e.keyCode == 27) window.location = ".."; + if (e.keyCode == 32) document.getElementById("toggle").checked = true; + } + </script> + {{/SiteEnableJS}} + {{/isSubalbum}} + </body> - {{#isSubalbum}} - <script type="text/javascript"> - window.onkeyup = function(e) { - if (e.keyCode == 27) window.location = ".."; - if (e.keyCode == 32) document.getElementById("toggle").checked = true; - } - </script> - {{/isSubalbum}} </html>
diff --git a/src/assets/picture.html b/src/assets/picture.html @@ -2,11 +2,13 @@ <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> - <title>ctucx' pics</title> + <title>{{name}} | {{SiteName}}</title> - <link type="text/css" rel="stylesheet" href="/style.css"> - <link rel="shortcut icon" href="favicon.ico"> + <meta name="description" content="{{SiteDescription}}"> + <meta name="keywords" content="{{SiteTags}}"> + <meta name="author" content="{{SiteAuthor}}"> + <meta name="generator" content="https://git.ctu.cx/ctucx/gallery"> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1.0, maximum-scale=1.0"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-capable" content="yes"> @@ -18,10 +20,13 @@ </head> <body> <header class="header header-view" style="display: flex;"> - <!--<div class="header-toolbar headerer-toolbar-photo header-toolbar-visible">--> <a class="button" href=".." id="button_close" title="Close Photo"><svg class="iconic"><use xlink:href="/iconic.svg#chevron-left"></use></svg></a> <a class="header-title">{{name}}<svg class="iconic "><use xlink:href="/iconic.svg#caret-bottom"></use></svg></a> - <a class="button" href="/originals/{{orig}}" title="Download"><svg class="iconic"><use xlink:href="/iconic.svg#cloud-download"></use></svg></a> + + {{#SiteShowOrigBtn}} + <a class="button" href="/originals{{orig}}" title="Download"><svg class="iconic"><use xlink:href="/iconic.svg#cloud-download"></use></svg></a> + {{/SiteShowOrigBtn}} + <a class="header-divider"></a> <input type="checkbox" id="toggle"> @@ -125,6 +130,11 @@ </tr> </tbody> </table> + {{^SiteEnableJS}} + <p> + Exif parsing is disabled by owner of this site. + </p> + {{/SiteEnableJS}} </div> </div> </header> @@ -148,33 +158,35 @@ </div> {{/hasNext}} </div> + {{#SiteEnableJS}} + <script type="text/javascript" src="/exif.js"></script> + <script type="text/javascript"> + window.onload=getExif; + window.onkeyup = function(e) { + if (e.keyCode == 27) window.location = ".."; + if (e.keyCode == 39) window.location = "../{{next_name}}"; + if (e.keyCode == 37) window.location = "../{{prev_name}}"; + if (e.keyCode == 32) document.getElementById("toggle").checked = true; + } + + function getExif() { + let image = document.getElementById("image"); + + EXIF.getData(image, function() { + document.getElementById("attr_resolution").innerHTML = EXIF.getTag(this, "PixelXDimension") + 'x' + EXIF.getTag(this, "PixelYDimension"); + + document.getElementById("attr_captured").innerHTML = EXIF.getTag(this, "DateTimeOriginal"); + document.getElementById("attr_make").innerHTML = EXIF.getTag(this, "Make"); + document.getElementById("attr_type/model").innerHTML = EXIF.getTag(this, "Model"); + document.getElementById("attr_shutter-speed").innerHTML = EXIF.getTag(this, "ExposureTime") + ' s'; + document.getElementById("attr_shutter-program").innerHTML = EXIF.getTag(this, "ExposureProgram"); + document.getElementById("attr_aperture").innerHTML = EXIF.getTag(this, "FNumber"); + document.getElementById("attr_focal-length").innerHTML = EXIF.getTag(this, "FocalLength") + 'mm'; + document.getElementById("attr_iso").innerHTML = EXIF.getTag(this, "ISOSpeedRatings"); + document.getElementById("attr_flash").innerHTML = EXIF.getTag(this, "Flash"); + }); + } + </script> + {{/SiteEnableJS}} </body> - <script type="text/javascript" src="/exif.js"></script> - <script type="text/javascript"> - window.onload=getExif; - window.onkeyup = function(e) { - if (e.keyCode == 27) window.location = ".."; - if (e.keyCode == 39) window.location = "../{{next_name}}"; - if (e.keyCode == 37) window.location = "../{{prev_name}}"; - if (e.keyCode == 32) document.getElementById("toggle").checked = true; - } - - function getExif() { - let image = document.getElementById("image"); - - EXIF.getData(image, function() { - document.getElementById("attr_resolution").innerHTML = EXIF.getTag(this, "PixelXDimension") + 'x' + EXIF.getTag(this, "PixelYDimension"); - - document.getElementById("attr_captured").innerHTML = EXIF.getTag(this, "DateTimeOriginal"); - document.getElementById("attr_make").innerHTML = EXIF.getTag(this, "Make"); - document.getElementById("attr_type/model").innerHTML = EXIF.getTag(this, "Model"); - document.getElementById("attr_shutter-speed").innerHTML = EXIF.getTag(this, "ExposureTime") + ' s'; - document.getElementById("attr_shutter-program").innerHTML = EXIF.getTag(this, "ExposureProgram"); - document.getElementById("attr_aperture").innerHTML = EXIF.getTag(this, "FNumber"); - document.getElementById("attr_focal-length").innerHTML = EXIF.getTag(this, "FocalLength") + 'mm'; - document.getElementById("attr_iso").innerHTML = EXIF.getTag(this, "ISOSpeedRatings"); - document.getElementById("attr_flash").innerHTML = EXIF.getTag(this, "Flash"); - }); - } - </script> </html>
diff --git a/src/gallery.nim b/src/gallery.nim @@ -1,4 +1,4 @@ -import os, osproc, options, json, strutils, sequtils, random, algorithm +import os, osproc, options, json, strutils, sequtils, random, algorithm, parsecfg, tables import moustachu const asset_exif_js = staticRead"./assets/exif.js" @@ -24,6 +24,31 @@ type desc*: Option[string] size*: BiggestInt +### +# +# Utils +# +### + +proc mergeJson(a: JsonNode, b: JsonNode): JsonNode = + result = a + for key, val in b: + result[key] = val + + +proc sortAlbums(x, y: Album): int = + if x.name < y.name: -1 + elif x.name == y.name: 0 + else: 1 + + +proc sortPictures(x, y: Picture): int = + if x.name < y.name: -1 + elif x.name == y.name: 0 + else: 1 + + + proc createPicture(path: string): Picture = let allowedExtensions = @[".jpg", ".jpeg", ".JPG", ".JPEG"] @@ -38,16 +63,6 @@ proc createPicture(path: string): Picture = result.filename = lastPathPart(path) result.size = getFileSize(path) -proc sortAlbums(x, y: Album): int = - if x.name < y.name: -1 - elif x.name == y.name: 0 - else: 1 - - -proc sortPictures(x, y: Picture): int = - if x.name < y.name: -1 - elif x.name == y.name: 0 - else: 1 proc createAlbum(path: string, isRoot: bool): Album = result.name = lastPathPart(path) @@ -70,34 +85,34 @@ proc createAlbum(path: string, isRoot: bool): Album = result.pictures.sort(sortPictures) -proc placeAssets(targetDir: string) = +proc placeAssets(targetDir: string, enableJS: bool) = echo "============" echo "Create Assets in target dir" discard existsOrCreateDir(targetDir) - writeFile(joinPath(targetDir, "exif.js"), asset_exif_js) writeFile(joinPath(targetDir, "style.css"), asset_style_css) writeFile(joinPath(targetDir, "no_images.svg"), asset_noimages_svg) writeFile(joinPath(targetDir, "iconic.svg"), asset_iconic_svg) + if enableJS: writeFile(joinPath(targetDir, "exif.js"), asset_exif_js) -proc generateWebsite(sourceDir: string, targetDir: string, album: Album) = +proc generateWebsite(sourceDir: string, targetDir: string, album: Album, config: JsonNode) = echo "============" echo "Create Album:" & album.name discard existsOrCreateDir(targetDir) discard existsOrCreateDir(joinPath(targetDir, "thumbnails")) discard existsOrCreateDir(joinPath(targetDir, "medium")) - var templateContext = %* { - "name": album.name, - "description": "-", - "numAlbums": album.subalbums.len, - "numPictures": album.pictures.len, - "isSubalbum": true, - "hasSubalbums": false, - "subalbums": [], - "pictures": [] - } + var templateContext = mergeJson(%* { + "name": album.name, + "description": "-", + "numAlbums": album.subalbums.len, + "numPictures": album.pictures.len, + "isSubalbum": true, + "hasSubalbums": false, + "subalbums": [], + "pictures": [] + }, config) var smallThumbnails = newSeq[string]() var mediumThumbnails = newSeq[string]() @@ -107,7 +122,7 @@ proc generateWebsite(sourceDir: string, targetDir: string, album: Album) = if album.subalbums.len > 0: templateContext["hasSubalbums"] = %true for subalbum in album.subalbums: - generateWebsite(sourceDir, joinPath(targetDir,subalbum.name), subalbum) + generateWebsite(sourceDir, joinPath(targetDir, subalbum.name), subalbum, config) var thumbnail1 = "/no_images.svg" var thumbnail2 = "/no_images.svg" @@ -119,25 +134,25 @@ proc generateWebsite(sourceDir: string, targetDir: string, album: Album) = thumbnail3 = subalbum.name & "/thumbnails/" & subalbum.pictures[rand(0..subalbum.pictures.len-1)].name & ".png" templateContext["subalbums"].add(%* { - "name": subalbum.name, - "numAlbums": subalbum.subalbums.len, - "numPictures": subalbum.pictures.len, - "thumbnail1": thumbnail1, - "thumbnail2": thumbnail2, - "thumbnail3": thumbnail3 - }) + "name": subalbum.name, + "numAlbums": subalbum.subalbums.len, + "numPictures": subalbum.pictures.len, + "thumbnail1": thumbnail1, + "thumbnail2": thumbnail2, + "thumbnail3": thumbnail3 + }) for index, picture in album.pictures: discard existsOrCreateDir(joinPath(targetDir, picture.name)) - var pictureTemplateContext = %* { - "name": picture.name, - "orig": picture.path.replace(sourceDir, "") & "/" & picture.filename, - "description": "-", - "hasPrev": false, - "hasNext": false, - "size": (picture.size.int/1000/1000) - } + var pictureTemplateContext = mergeJson(%* { + "name": picture.name, + "orig": picture.path.replace(sourceDir, "") & "/" & picture.filename, + "description": "-", + "hasPrev": false, + "hasNext": false, + "size": (picture.size.int/1000/1000) + }, config) if not picture.desc.isNone: pictureTemplateContext["description"] = %picture.desc.get @@ -149,19 +164,19 @@ proc generateWebsite(sourceDir: string, targetDir: string, album: Album) = pictureTemplateContext["hasNext"] = %true pictureTemplateContext["next_name"] = %album.pictures[index+1].name + echo "Generate picture page: " & picture.name writeFile(joinPath(targetDir, picture.name, "index.html"), render(asset_picture_html, pictureTemplateContext)) - if not fileExists(targetDir & "/thumbnails/" & picture.name & ".png"): - smallThumbnails.add("/usr/bin/env mogrify -strip -quality 90 -format png -path " & quoteShell(joinPath(targetDir, "thumbnails")) & " -thumbnail 200x200^ -gravity center -extent 200x200 " & quoteShell(joinPath(picture.path, picture.filename))) + smallThumbnails.add("/usr/bin/env mogrify -strip -quality " & $config["ThumbThumbQuality"].getInt & " -format png -path " & quoteShell(joinPath(targetDir, "thumbnails")) & " -thumbnail " & $config["ThumbThumbMaxWidth"].getInt & "x" & $config["ThumbThumbMaxHeight"].getInt & "^ -gravity center -extent " & $config["ThumbThumbMaxWidth"].getInt & "x" & $config["ThumbThumbMaxHeight"].getInt & " " & quoteShell(joinPath(picture.path, picture.filename))) if not fileExists(targetDir & "/medium/" & picture.name & ".jpg"): - mediumThumbnails.add("/usr/bin/env mogrify -format jpg -path " & quoteShell(joinPath(targetDir, "medium")) & " -resize 1920x\\> " & quoteShell(joinPath(picture.path, picture.filename))) + mediumThumbnails.add("/usr/bin/env mogrify -format jpg -path " & quoteShell(joinPath(targetDir, "medium")) & " -resize " & $config["ThumbMediumMaxWidth"].getInt & "x\\> " & quoteShell(joinPath(picture.path, picture.filename))) templateContext["pictures"].add(%* { - "name": picture.name, - }) + "name": picture.name, + }) echo "Generate small thumbnails!" discard execProcesses(smallThumbnails) @@ -175,28 +190,89 @@ proc generateWebsite(sourceDir: string, targetDir: string, album: Album) = proc main = - randomize() + randomize() + + if (execProcess("/usr/bin/env mogrify -v").contains("No such file or directory")): + echo "It seems like you don't have ImageMagick installed, which is mandatory to use this tool.\nBye!" + quit() + + if not fileExists(paramStr(1)): + echo "The given config-file doesn't exist. Do u wanna write a default one to it? [y/N]" + + if readLine(stdin) == "y": + var config = newConfig() + config.setSectionKey("", "SourceDir", "./foobar") + config.setSectionKey("", "TargetDir", "./out") + + config.setSectionKey("Site", "Author", "ctucx") + config.setSectionKey("Site", "Name", "ctucx' bahnbilder") + config.setSectionKey("Site", "Description", "a short discription for your site") + config.setSectionKey("Site", "Tags", "a list of tags for seo stuff") + config.setSectionKey("Site", "ShowOriginalsButton", "true") + config.setSectionKey("Site", "SymlinkOriginals", "false") + + config.setSectionKey("Thumbnails", "MediumMaxWidth", "1920") + config.setSectionKey("Thumbnails", "MediumMaxHeight", "1080") + config.setSectionKey("Thumbnails", "ThumbMaxWidth", "200") + config.setSectionKey("Thumbnails", "ThumbMaxHeight", "200") + config.setSectionKey("Thumbnails", "ThumbQuality", "90") + config.setSectionKey("Thumbnails", "EnableJS", "true") + + config.writeConfig(paramStr(1)) + echo "Have written a default config to this file: " & paramStr(1) + echo "Please check it and rerun this program." + quit() + + var config = %* {} + + try: + var configFile = loadConfig(paramStr(1)) + config = %* { + "SourceDir": configFile.getSectionValue("", "SourceDir"), + "TargetDir": configFile.getSectionValue("", "TargetDir"), + "SiteName": configFile.getSectionValue("Site", "Name"), + "SiteAuthor": configFile.getSectionValue("Site", "Author"), + "SiteDescription": configFile.getSectionValue("Site", "Description"), + "SiteTags": configFile.getSectionValue("Site", "Tags"), + "SiteShowOrigBtn": configFile.getSectionValue("Site", "ShowOriginalsButton").parseBool, #not implemented yet + "SiteSymlinkOrig": configFile.getSectionValue("Site", "SymlinkOriginals").parseBool, #not implemented yet + "SiteEnableJS": configFile.getSectionValue("Site", "EnableJS").parseBool, + "ThumbMediumMaxWidth": configFile.getSectionValue("Thumbnail", "MediumMaxWidth").parseInt, #not implemented yet + "ThumbMediumMaxHeight": configFile.getSectionValue("Thumbnail", "MediumMaxHeight").parseInt, #not implemented yet + "ThumbThumbMaxWidth": configFile.getSectionValue("Thumbnail", "ThumbMaxWidth").parseInt, #not implemented yet + "ThumbThumbMaxHeight": configFile.getSectionValue("Thumbnail", "ThumbMaxHeight").parseInt, #not implemented yet + "ThumbThumbQuality": configFile.getSectionValue("Thumbnail", "ThumbQuality").parseInt #not implemented yet + } + + except ValueError: + let + e = getCurrentException() + msg = getCurrentExceptionMsg() - if (execProcess("/usr/bin/env mogrify -v").contains("No such file or directory")): - echo "It seems like you don't have ImageMagick installed, which is mandatory to use this tool.\nBye!" - quit() + echo "Got exception while parsing config: ", repr(e), " with message ", msg + quit() - let sourceDir = normalizedPath(paramStr(1)) - let targetDir = normalizedPath(paramStr(2)) + except: + echo "Unknown exception while parsing of configuration!" + quit() - if not dirExists(sourceDir): - echo "The source directory does not exist!\nBye!" - quit() + if not dirExists(config["SourceDir"].getStr): + echo "The source directory does not exist!\nBye!" + quit() - if not dirExists(targetDir): - echo "The target directory does not exist!\nBye!" - quit() + if not dirExists(config["TargetDir"].getStr): + echo "The target directory does not exist!\nBye!" + quit() + let mainAlbum = createAlbum(config["SourceDir"].getStr, true) - let mainAlbum = createAlbum(sourceDir, true) + if config["SiteSymlinkOrig"].getBool != false: + if not symlinkExists(joinPath(config["TargetDir"].getStr, "originals")): + createSymlink(config["SourceDir"].getStr, joinPath(config["TargetDir"].getStr, "originals")) + + placeAssets(config["TargetDir"].getStr, config["SiteEnableJS"].getBool) + generateWebsite(config["SourceDir"].getStr, config["TargetDir"].getStr, mainAlbum, config) - placeAssets(targetDir) - generateWebsite(sourceDir, targetDir, mainAlbum) main()