From 1fa2c55a84095e2d21f72a402c6525f2623e161a Mon Sep 17 00:00:00 2001 From: Julien Malik Date: Thu, 18 Feb 2016 14:27:13 +0100 Subject: [PATCH 1/6] [enh] Same cache logic for thumbs and fullsize pictures - store gm settings in the cache as a dict - generalize usage of the gm settings to resize - can be used to resize fullsize versions of the pictures - without -quality switch, gm defaults to -quality 75. use -define jpeg:preserve-settings for fullsize versions to avoid altering them - do not store "name" in the cache, because when the same image is used in a gallery and as the cover, it invalidates the cache entry and becomes always generated --- prosopopee/cache.py | 32 +++++++---------------- prosopopee/prosopopee.py | 56 ++++++++++++++++++++++------------------ 2 files changed, 41 insertions(+), 47 deletions(-) diff --git a/prosopopee/cache.py b/prosopopee/cache.py index cbbd79f..27eeae5 100644 --- a/prosopopee/cache.py +++ b/prosopopee/cache.py @@ -3,6 +3,10 @@ import json CACHE_VERSION = 1 +def remove_name(options): + noname_options = options.copy() + del noname_options["name"] + return noname_options class Cache(object): cache_file_path = os.path.join(os.getcwd(), ".prosopopee_cache") @@ -22,39 +26,23 @@ class Cache(object): print "info: cache format as changed, prune cache" self.cache = {"version": CACHE_VERSION} - def thumbnail_needs_to_be_generated(self, source, target, image): + + def needs_to_be_generated(self, source, target, options): if not os.path.exists(target): return True if target not in self.cache: return True - cached_thumbnail = self.cache[target] + cached_picture = self.cache[target] - if cached_thumbnail["size"] != os.path.getsize(source) or cached_thumbnail["options"] != image.options: + if cached_picture["size"] != os.path.getsize(source) or cached_picture["options"] != remove_name(options): return True return False - def image_needs_to_be_oritend(self, source, target, command): - if not os.path.exists(target): - return True - - if target not in self.cache: - return True - - cached_image = self.cache[target] - - if cached_image["size"] != os.path.getsize(source) or cached_image["command"] != command: - return True - - return False - - def cache_thumbnail(self, source, target, image): - self.cache[target] = {"size": os.path.getsize(source), "options": image.options} - - def cache_auto_oriented_image(self, source, target, command): - self.cache[target] = {"size": os.path.getsize(source), "command": command} + def cache_picture(self, source, target, options): + self.cache[target] = {"size": os.path.getsize(source), "options": remove_name(options)} def __del__(self): self.json.dump(self.cache, open(self.cache_file_path, "w")) diff --git a/prosopopee/prosopopee.py b/prosopopee/prosopopee.py index 6400687..1917ef9 100644 --- a/prosopopee/prosopopee.py +++ b/prosopopee/prosopopee.py @@ -18,7 +18,9 @@ page_template = templates.get_template("page.html") SETTINGS = { "gm": { "quality": 75, - "auto-orient": True + "auto-orient": True, + "strip": True, + "resize": None } } @@ -45,6 +47,24 @@ class Image(object): def autoorient(self): return self.options["auto-orient"] + def gm(self, source, target, options): + if CACHE.needs_to_be_generated(source, target, options): + gm_switches = { + "source": source, + "target": target, + "auto-orient" : "-auto-orient" if options["auto-orient"] else "", + "strip": "-strip" if options["strip"] else "", + "quality": "-quality %s" % options["quality"] if options.has_key("quality") else "-define jpeg:preserve-settings", + "resize": "-resize %s" % options["resize"] if options.has_key("resize") else "" + } + command = "gm convert {source} {auto-orient} {strip} {quality} {resize} {target}".format(**gm_switches) + print command + os.system(command) + CACHE.cache_picture(source, target, options) + else: + print "skipped %s since it's already generated (based on source unchanged size and images options set in your gallery's settings.yaml)" % target + + def copy(self): source, target = os.path.join(self.base_dir, self.name), os.path.join(self.target_dir, self.name) @@ -52,40 +72,26 @@ class Image(object): # if os.path.exists(target) and os.path.getsize(source) == os.path.getsize(target): # print "Skipped %s since the file hasn't been modified based on file size" % source # return "" - if not self.autoorient: + + options = self.options.copy() + if not options["auto-orient"] and not options["strip"]: shutil.copyfile(source, target) print source, "->", target - return "" - - command = "gm convert %s -strip -auto-orient %s" % (source, target) - - if CACHE.image_needs_to_be_oritend(source, target, command): - print command - os.system(command) - CACHE.cache_auto_oriented_image(source, target, command) else: - print "skipped %s since it's already generated (based on source unchanged size and images options)" % target - + # Do not consider quality settings here, since we aim to copy the input image + # better to preserve input encoding setting + del options["quality"] + self.gm(source, target, options) return "" def generate_thumbnail(self, gm_geometry): thumbnail_name = self.name.split(".") thumbnail_name[-2] += "-%s" % gm_geometry thumbnail_name = ".".join(thumbnail_name) - source, target = os.path.join(self.base_dir, self.name), os.path.join(self.target_dir, thumbnail_name) - - if CACHE.thumbnail_needs_to_be_generated(source, target, self): - gm_options = "" - if self.autoorient: - gm_options = "-auto-orient" - command = "gm convert %s -strip %s -resize %s -quality %s %s" % (source, gm_options, gm_geometry, self.quality, target) - print command - os.system(command) - CACHE.cache_thumbnail(source, target, self) - else: - print "skipped %s since it's already generated (based on source unchanged size and images options set in your gallery's settings.yaml)" % target - + options = self.options.copy() + options.update({"resize": gm_geometry}) + self.gm(source, target, options) return thumbnail_name def __repr__(self): From 0dd6e39b746481d2ab9dc30af91019807676db3a Mon Sep 17 00:00:00 2001 From: Julien Malik Date: Thu, 18 Feb 2016 15:28:53 +0100 Subject: [PATCH 2/6] [doc] document gm settings customization. also fix some typos. --- README.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index eec6109..96977bd 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ title: My exploration of the outside world sub_title: it's a scary place, don't go there ``` +It can also optionally contain a menu and global settings. + #### Menu It is possible to add a menu to your homepage that links to static pages. To do so, add a `menu` key to your `settings.yaml`, for example: @@ -88,6 +90,30 @@ entry like any other gallery. **NOTE**: except the "static: " option to disepear quite soon for a more generic approach to "choose your page style". +#### Global settings + +Global settings can be set in your root `settings.yaml`, under the `settings` key. + +Currently a `gm` settings key allows to customize the default GraphicsMagick's behavior. It looks like : + +```yaml +settings: + gm: + quality: 75 + auto-orient: True + strip: True + resize: 50% +``` + +The meaning of the currently supported GraphicsMagick's settings is as follows : +* `quality` allows to customize the compression level of thumbnails (between 0 and 100) +* `auto-orient` change the orientation of pictures so they are upright (based on corresponding EXIF tags if present) +* `strip` removes all profiles and text attributes from the image (good for privacy, slightly reduce file size) +* `resize` can be used to resize the fullsize version of pictures. by default, input image size is preserved + +Any GraphicsMagick setting can be customized on a per-image basis (either `cover` or `image`, see below). + + ### Gallery settings.yaml This settings.yaml will describe: @@ -142,15 +168,39 @@ sections: - ... ``` +Images go into the `cover` or `image` keys. +Each image individual processing settings can be customized to override the default +GraphicsMagick settings defined (or not) in the root `settings.yaml`. + +This is done by putting the image path into a `name` key, +and adding specific processing settings afterwards. + +For example, you can replace : + +```yaml +image: image1.jpg +``` + +by : + +```yaml +image: + name: image1.jpg + quality: 90 + strip: False + auto-orient: False +``` + + ### Different kind of sections -A gallery is compose of a succession of sections as you can on this [wonderfully +A gallery is composed of a succession of sections as you can see on this [wonderfully totally uninteresting example gallery](http://psycojoker.github.io/prosopopee/first_gallery/) the gallery is composed of 5 sections: * a full screen picture with text written on it -* a picture with with borders around it +* a picture with borders around it * a group of 5 pictures * and a fullscreen picture without text on it this time @@ -167,7 +217,7 @@ this is not mandatory. #### Full Screen picture with OR without text on it -This display a full screen picture as shown in the [example +This displays a full screen picture as shown in the [example gallery](http://psycojoker.github.io/prosopopee/first_gallery/) in the first and last sections. How you should use it: @@ -191,7 +241,7 @@ Without text: #### Bordered picture -This display a centered picture that is surrounded by white (the background) as +This displays a centered picture that is surrounded by white (the background) as shown in the second position of the [example gallery](http://psycojoker.github.io/prosopopee/first_gallery/). @@ -204,8 +254,8 @@ How to use it: #### Group of pictures -This display a group of zoomable pictures on one or multiple lines as shown on -the forth position (after the text) of the [example +This displays a group of zoomable pictures on one or multiple lines as shown on +the fourth position (after the text) of the [example gallery](http://psycojoker.github.io/prosopopee/first_gallery/). ```yaml @@ -220,14 +270,15 @@ gallery](http://psycojoker.github.io/prosopopee/first_gallery/). - image5.jpg ``` -Every sublist (the first level - represent a line). +The first level `-` represent a line of pictures. +The second level `-` represent the list of images in this line. **Know bug**: the images are left aligned, so if you don't put enough images on a line, you'll have white space on the right. #### Text -This display some centered text as shown on the third position of the [example +This displays some centered text as shown on the third position of the [example gallery](http://psycojoker.github.io/prosopopee/first_gallery/). HTML is allowed inside the text. @@ -240,8 +291,8 @@ How to use it: #### Paragraph -This display a h2 title followed by text. HTML is allowed inside of the text. -If not title is declared, a separator is added. +This displays a h2 title followed by text. HTML is allowed inside of the text. +If no title is declared, a separator is added. How to use it: @@ -264,7 +315,7 @@ How to use it: #### Panorama -This display a very large pictures with a drag and drop posibility on it. +This displays a very large picture with a drag-and-drop possibility on it. How to use it: From 70bb6b94e9a9807a34228951e39bdb5a0cd7600b Mon Sep 17 00:00:00 2001 From: Julien Malik Date: Thu, 18 Feb 2016 15:42:43 +0100 Subject: [PATCH 3/6] [enh] add a .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc From 6c1d9c3069aa1d4b22bc37a3d0c20a78c2eb32a1 Mon Sep 17 00:00:00 2001 From: Julien Malik Date: Thu, 18 Feb 2016 15:51:52 +0100 Subject: [PATCH 4/6] [fix] prevent rendering None as a string --- prosopopee/prosopopee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prosopopee/prosopopee.py b/prosopopee/prosopopee.py index 1917ef9..fd56867 100644 --- a/prosopopee/prosopopee.py +++ b/prosopopee/prosopopee.py @@ -55,7 +55,7 @@ class Image(object): "auto-orient" : "-auto-orient" if options["auto-orient"] else "", "strip": "-strip" if options["strip"] else "", "quality": "-quality %s" % options["quality"] if options.has_key("quality") else "-define jpeg:preserve-settings", - "resize": "-resize %s" % options["resize"] if options.has_key("resize") else "" + "resize": "-resize %s" % options["resize"] if options.has_key("resize") and options["resize"] is not None else "" } command = "gm convert {source} {auto-orient} {strip} {quality} {resize} {target}".format(**gm_switches) print command From 121a37dea1d2376beb42e2b200433806b220c21d Mon Sep 17 00:00:00 2001 From: Julien Malik Date: Thu, 18 Feb 2016 15:54:24 +0100 Subject: [PATCH 5/6] [fix] remove unused properties --- prosopopee/prosopopee.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/prosopopee/prosopopee.py b/prosopopee/prosopopee.py index fd56867..d546d3e 100644 --- a/prosopopee/prosopopee.py +++ b/prosopopee/prosopopee.py @@ -39,14 +39,6 @@ class Image(object): def name(self): return self.options["name"] - @property - def quality(self): - return self.options["quality"] - - @property - def autoorient(self): - return self.options["auto-orient"] - def gm(self, source, target, options): if CACHE.needs_to_be_generated(source, target, options): gm_switches = { From c71008a5ced6d5bbd1f85702db3c693a43e16084 Mon Sep 17 00:00:00 2001 From: Julien Malik Date: Fri, 19 Feb 2016 08:51:18 +0100 Subject: [PATCH 6/6] [enh] be more pythonic --- prosopopee/prosopopee.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/prosopopee/prosopopee.py b/prosopopee/prosopopee.py index d546d3e..e8ac12b 100644 --- a/prosopopee/prosopopee.py +++ b/prosopopee/prosopopee.py @@ -46,8 +46,8 @@ class Image(object): "target": target, "auto-orient" : "-auto-orient" if options["auto-orient"] else "", "strip": "-strip" if options["strip"] else "", - "quality": "-quality %s" % options["quality"] if options.has_key("quality") else "-define jpeg:preserve-settings", - "resize": "-resize %s" % options["resize"] if options.has_key("resize") and options["resize"] is not None else "" + "quality": "-quality %s" % options["quality"] if "quality" in options else "-define jpeg:preserve-settings", + "resize": "-resize %s" % options["resize"] if options.get("resize", None) is not None else "" } command = "gm convert {source} {auto-orient} {strip} {quality} {resize} {target}".format(**gm_switches) print command @@ -56,7 +56,6 @@ class Image(object): else: print "skipped %s since it's already generated (based on source unchanged size and images options set in your gallery's settings.yaml)" % target - def copy(self): source, target = os.path.join(self.base_dir, self.name), os.path.join(self.target_dir, self.name)