From fc82ddc1abb8234a3c013206336db97cd24d595a Mon Sep 17 00:00:00 2001 From: Adrien Beudin Date: Fri, 29 Apr 2016 12:40:34 +0200 Subject: [PATCH] start video support --- prosopopee/prosopopee.py | 112 ++++++++++++++---- .../themes/exposure/static/css/style-page.css | 23 +++- .../exposure/static/js/jquery.lazyload.min.js | 2 + .../exposure/templates/gallery-index.html | 26 ++++ .../templates/sections/pictures-group.html | 26 ++-- .../themes/material/static/css/styles.css | 17 +++ .../material/static/js/jquery.lazyload.min.js | 2 + .../material/templates/gallery-index.html | 103 +++++++++------- .../templates/sections/pictures-group.html | 62 ++++++---- 9 files changed, 278 insertions(+), 95 deletions(-) create mode 100644 prosopopee/themes/exposure/static/js/jquery.lazyload.min.js create mode 100644 prosopopee/themes/material/static/js/jquery.lazyload.min.js diff --git a/prosopopee/prosopopee.py b/prosopopee/prosopopee.py index 275cf94..3d32115 100644 --- a/prosopopee/prosopopee.py +++ b/prosopopee/prosopopee.py @@ -23,9 +23,76 @@ SETTINGS = { "auto-orient": True, "strip": True, "resize": None + }, + "ffmpeg": { + "loglevel": "panic", + "format": "webm", + "resolution": "1280x720", + "bitrate": "3900k", + "preselect": "libvpx-720p" } } +class Video(object): + base_dir = "" + target_dir = "" + + def __init__(self, options): + # assuming string + if not isinstance(options, dict): + options = {"video": options} + self.options = SETTINGS["ffmpeg"].copy() # used for caching, if it's modified -> regenerate + self.options.update(options) + + @property + def name(self): + return self.options["name"] + + def ffmpeg(self, source, target, options): + if CACHE.needs_to_be_generated(source, target, options): + ffmpeg_switches = { + "source": source, + "target": target, + "loglevel": "-loglevel %s" % options["loglevel"], + "resolution": "-s %s" % options["resolution"], + "preselect": "-vpre %s" % options["preselect"], + "resize": "-vf scale=-1:%s" % options.get("resize"), + "bitrate": "-b %s" % options["bitrate"], + "format": "-f %s" % options["format"] + } + warning("Generation", source) + if options.get("resize"): + command = "ffmpeg {loglevel} -i {source} {resize} -vframes 1 -y {target}".format(**ffmpeg_switches) + os.system(command) + else: + command = "ffmpeg {loglevel} -i {source} {resolution} {preselect} {bitrate} -pass 1 -an {format} -y {target}".format(**ffmpeg_switches) + command2 = "ffmpeg {loglevel} -i {source} {resolution} {preselect} {bitrate} -pass 2 -acodec libvorbis -ab 100k {format} -y {target}".format(**ffmpeg_switches) + os.system(command) + os.system(command2) + CACHE.cache_picture(source, target, options) + else: + okgreen("Skipped", source + " is already generated") + + def copy(self): + source, target = os.path.join(self.base_dir, self.name), os.path.join(self.target_dir, self.name) + options = self.options.copy() + self.ffmpeg(source, target, options) + return "" + + def generate_thumbnail(self, gm_geometry): + thumbnail_name = self.name.split(".") + thumbnail_name[-2] += "-%s" % gm_geometry + thumbnail_name = thumbnail_name[-2] + ".png" + source, target = os.path.join(self.base_dir, self.name), os.path.join(self.target_dir, thumbnail_name) + options = self.options.copy() + options.update({"resize": gm_geometry}) + self.ffmpeg(source, target, options) + return thumbnail_name + + def __repr__(self): + return self.name + + class Image(object): base_dir = "" target_dir = "" @@ -44,14 +111,13 @@ class Image(object): 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 "quality" in options else "-define jpeg:preserve-settings", - "resize": "-resize %s" % options["resize"] if options.get("resize", None) is not None else "" - - } + "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 "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) warning("Generation", source) os.system(command) @@ -72,7 +138,7 @@ class Image(object): shutil.copyfile(source, target) print source, "->", target else: - # Do not consider quality settings here, since we aim to copy the input image + # 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) @@ -95,11 +161,11 @@ class Image(object): def main(): if os.system("which gm > /dev/null") != 0: sys.stderr.write("ERROR: I can't locate the 'gm' binary, I won't be able to resize " - "images, please install the 'graphicsmagick' package.\n") + "images, please install the 'graphicsmagick' package.\n") sys.exit(1) error(os.path.exists(os.path.join(os.getcwd(), "settings.yaml")), "I can't find a " - "settings.yaml in the current working directory") + "settings.yaml in the current working directory") settings = yaml.safe_load(open("settings.yaml", "r")) @@ -112,7 +178,7 @@ def main(): if (settings["rss"] or settings["share"]) and not settings.get("url"): warning("warning", "If you want the rss and/or the social network share to work, " - "you need to specify the website url in root settings") + "you need to specify the website url in root settings") settings["rss"] = False settings["share"] = False @@ -124,8 +190,8 @@ def main(): dirs = filter(lambda x: x not in (".", "..") and os.path.isdir(x) and os.path.exists(os.path.join(os.getcwd(), x, "settings.yaml")), os.listdir(os.getcwd())) error(dirs, "I can't find at least one directory with a settings.yaml in the current working " - "directory (NOT the settings.yaml in your current directory, but one INSIDE A " - "DIRECTORY in your current working directory), you don't have any gallery?") + "directory (NOT the settings.yaml in your current directory, but one INSIDE A " + "DIRECTORY in your current working directory), you don't have any gallery?") if not os.path.exists("build"): os.makedirs("build") @@ -143,7 +209,7 @@ def main(): templates = Environment(loader=FileSystemLoader([ prosopopee_templates_dir, project_templates_dir - ])) + ])) index_template = templates.get_template("index.html") gallery_index_template = templates.get_template("gallery-index.html") @@ -167,12 +233,12 @@ def main(): if gallery_settings.get("public", True): error(gallery_settings.get("title"), "Your gallery describe in %s need to have a " - "title" % (os.path.join(gallery, "settings.yaml"))) + "title" % (os.path.join(gallery, "settings.yaml"))) error(gallery_settings.get("cover"), "You should specify a path to a cover picture " - "in %s" % (os.path.join(gallery, "settings.yaml"))) + "in %s" % (os.path.join(gallery, "settings.yaml"))) cover_image_path = os.path.join(gallery, gallery_settings["cover"]) error(os.path.exists(cover_image_path), "File for %s cover image doesn't exist at " - "%s" % (gallery, cover_image_path)) + "%s" % (gallery, cover_image_path)) front_page_galleries_cover.append({ "title": gallery_settings["title"], @@ -181,17 +247,19 @@ def main(): "date": gallery_settings.get("date", ""), "tags": gallery_settings.get("tags", ""), "cover": cover_image_path, - }) + }) - if not os.path.exists(os.path.join("build", gallery)): - os.makedirs(os.path.join("build", gallery)) + if not os.path.exists(os.path.join("build", gallery)): + os.makedirs(os.path.join("build", gallery)) # this should probably be a factory Image.base_dir = os.path.join(os.getcwd(), gallery) Image.target_dir = os.path.join(os.getcwd(), "build", gallery) + Video.base_dir = os.path.join(os.getcwd(), gallery) + Video.target_dir = os.path.join(os.getcwd(), "build", gallery) template_to_render = page_template if gallery_settings.get("static") else gallery_index_template - open(os.path.join("build", gallery, "index.html"), "w").write(template_to_render.render(settings=settings, gallery=gallery_settings, Image=Image, link=gallery).encode("Utf-8")) + open(os.path.join("build", gallery, "index.html"), "w").write(template_to_render.render(settings=settings, gallery=gallery_settings, Image=Image, Video=Video, link=gallery).encode("Utf-8")) if settings["rss"]: open(os.path.join("build", "feed.xml"), "w").write(feed_template.render(settings=settings, link=gallery, galleries=reversed(sorted(front_page_galleries_cover, key=lambda x: x["date"]))).encode("Utf-8")) diff --git a/prosopopee/themes/exposure/static/css/style-page.css b/prosopopee/themes/exposure/static/css/style-page.css index cedb963..7dd1cef 100644 --- a/prosopopee/themes/exposure/static/css/style-page.css +++ b/prosopopee/themes/exposure/static/css/style-page.css @@ -9,11 +9,12 @@ body { width: 100%; background-color: #FBFBFB; color: black; - margin: 0; + margin: 0px; } section { margin-bottom: 80px; + margin-top: 40px; } a { @@ -27,6 +28,7 @@ a { height: 100%; width: 100%; min-height: 250px; + margin-top: 0; } .full-picture > .picture-text { @@ -90,7 +92,6 @@ a { .pictures-line .picture img { width: 100%; - height: 100%; } .pictures-line .separator { @@ -336,3 +337,21 @@ a.google { line-height: normal; color: #333; } + +.picture video { + position: absolute; + top: 0; + left: 0; + z-index: 2; + width: 100%; + height: auto; +} + +.bg-section { + padding: 1px 0px; +} + +.bg-section section { + margin-bottom: 40px; + margin-top: 40px; +} diff --git a/prosopopee/themes/exposure/static/js/jquery.lazyload.min.js b/prosopopee/themes/exposure/static/js/jquery.lazyload.min.js new file mode 100644 index 0000000..0403807 --- /dev/null +++ b/prosopopee/themes/exposure/static/js/jquery.lazyload.min.js @@ -0,0 +1,2 @@ +/*! Lazy Load 1.9.7 - MIT license - Copyright 2010-2015 Mika Tuupola */ +!function(a,b,c,d){var e=a(b);a.fn.lazyload=function(f){function g(){var b=0;i.each(function(){var c=a(this);if(!j.skip_invisible||c.is(":visible"))if(a.abovethetop(this,j)||a.leftofbegin(this,j));else if(a.belowthefold(this,j)||a.rightoffold(this,j)){if(++b>j.failure_limit)return!1}else c.trigger("appear"),b=0})}var h,i=this,j={threshold:0,failure_limit:0,event:"scroll",effect:"show",container:b,data_attribute:"original",skip_invisible:!1,appear:null,load:null,placeholder:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"};return f&&(d!==f.failurelimit&&(f.failure_limit=f.failurelimit,delete f.failurelimit),d!==f.effectspeed&&(f.effect_speed=f.effectspeed,delete f.effectspeed),a.extend(j,f)),h=j.container===d||j.container===b?e:a(j.container),0===j.event.indexOf("scroll")&&h.bind(j.event,function(){return g()}),this.each(function(){var b=this,c=a(b);b.loaded=!1,(c.attr("src")===d||c.attr("src")===!1)&&c.is("img")&&c.attr("src",j.placeholder),c.one("appear",function(){if(!this.loaded){if(j.appear){var d=i.length;j.appear.call(b,d,j)}a("").bind("load",function(){var d=c.attr("data-"+j.data_attribute);c.hide(),c.is("img")?c.attr("src",d):c.css("background-image","url('"+d+"')"),c[j.effect](j.effect_speed),b.loaded=!0;var e=a.grep(i,function(a){return!a.loaded});if(i=a(e),j.load){var f=i.length;j.load.call(b,f,j)}}).attr("src",c.attr("data-"+j.data_attribute))}}),0!==j.event.indexOf("scroll")&&c.bind(j.event,function(){b.loaded||c.trigger("appear")})}),e.bind("resize",function(){g()}),/(?:iphone|ipod|ipad).*os 5/gi.test(navigator.appVersion)&&e.bind("pageshow",function(b){b.originalEvent&&b.originalEvent.persisted&&i.each(function(){a(this).trigger("appear")})}),a(c).ready(function(){g()}),this},a.belowthefold=function(c,f){var g;return g=f.container===d||f.container===b?(b.innerHeight?b.innerHeight:e.height())+e.scrollTop():a(f.container).offset().top+a(f.container).height(),g<=a(c).offset().top-f.threshold},a.rightoffold=function(c,f){var g;return g=f.container===d||f.container===b?e.width()+e.scrollLeft():a(f.container).offset().left+a(f.container).width(),g<=a(c).offset().left-f.threshold},a.abovethetop=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollTop():a(f.container).offset().top,g>=a(c).offset().top+f.threshold+a(c).height()},a.leftofbegin=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollLeft():a(f.container).offset().left,g>=a(c).offset().left+f.threshold+a(c).width()},a.inviewport=function(b,c){return!(a.rightoffold(b,c)||a.leftofbegin(b,c)||a.belowthefold(b,c)||a.abovethetop(b,c))},a.extend(a.expr[":"],{"below-the-fold":function(b){return a.belowthefold(b,{threshold:0})},"above-the-top":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-screen":function(b){return a.rightoffold(b,{threshold:0})},"left-of-screen":function(b){return!a.rightoffold(b,{threshold:0})},"in-viewport":function(b){return a.inviewport(b,{threshold:0})},"above-the-fold":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-fold":function(b){return a.rightoffold(b,{threshold:0})},"left-of-fold":function(b){return!a.rightoffold(b,{threshold:0})}})}(jQuery,window,document); \ No newline at end of file diff --git a/prosopopee/themes/exposure/templates/gallery-index.html b/prosopopee/themes/exposure/templates/gallery-index.html index 36d573f..2f0ea0d 100644 --- a/prosopopee/themes/exposure/templates/gallery-index.html +++ b/prosopopee/themes/exposure/templates/gallery-index.html @@ -34,6 +34,7 @@ + {% include "footer.html" %} diff --git a/prosopopee/themes/exposure/templates/sections/pictures-group.html b/prosopopee/themes/exposure/templates/sections/pictures-group.html index cc48ba3..037203c 100644 --- a/prosopopee/themes/exposure/templates/sections/pictures-group.html +++ b/prosopopee/themes/exposure/templates/sections/pictures-group.html @@ -1,22 +1,34 @@ {% if section.background %} -
-{% endif %} -
- {% for line in section.images %} +
+ {% endif %} +
+ {% for line in section.images %}
{% for image in line %} + {% if image.type == "video" %} + {% set video = Video(image) %} + {{ video.copy() }} + {% else %} {% set caption = image.text %} {% set image = Image(image) %} {{ image.copy() }} + {% endif %}
+ {% if video %} + + + {% else %} - + {% if caption %}
{{ caption }}
{% endif %}
+ {% endif %}
{% if not loop.last %}
@@ -24,7 +36,7 @@ {% endfor %}
{% endfor %} -
-{% if section.background %} +
+ {% if section.background %}
{% endif %} diff --git a/prosopopee/themes/material/static/css/styles.css b/prosopopee/themes/material/static/css/styles.css index 7b5bdd9..d92001a 100644 --- a/prosopopee/themes/material/static/css/styles.css +++ b/prosopopee/themes/material/static/css/styles.css @@ -126,3 +126,20 @@ main { width: 100%; height: 100%; } + +.picture video { + position: absolute; + top: 0; + left: 0; + z-index: 2; + width: 100%; + height: auto; +} + +div#bg-section { + padding: 1px 0px; +} + +.pictures-group .row { + margin-top: 20px +} diff --git a/prosopopee/themes/material/static/js/jquery.lazyload.min.js b/prosopopee/themes/material/static/js/jquery.lazyload.min.js new file mode 100644 index 0000000..0403807 --- /dev/null +++ b/prosopopee/themes/material/static/js/jquery.lazyload.min.js @@ -0,0 +1,2 @@ +/*! Lazy Load 1.9.7 - MIT license - Copyright 2010-2015 Mika Tuupola */ +!function(a,b,c,d){var e=a(b);a.fn.lazyload=function(f){function g(){var b=0;i.each(function(){var c=a(this);if(!j.skip_invisible||c.is(":visible"))if(a.abovethetop(this,j)||a.leftofbegin(this,j));else if(a.belowthefold(this,j)||a.rightoffold(this,j)){if(++b>j.failure_limit)return!1}else c.trigger("appear"),b=0})}var h,i=this,j={threshold:0,failure_limit:0,event:"scroll",effect:"show",container:b,data_attribute:"original",skip_invisible:!1,appear:null,load:null,placeholder:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"};return f&&(d!==f.failurelimit&&(f.failure_limit=f.failurelimit,delete f.failurelimit),d!==f.effectspeed&&(f.effect_speed=f.effectspeed,delete f.effectspeed),a.extend(j,f)),h=j.container===d||j.container===b?e:a(j.container),0===j.event.indexOf("scroll")&&h.bind(j.event,function(){return g()}),this.each(function(){var b=this,c=a(b);b.loaded=!1,(c.attr("src")===d||c.attr("src")===!1)&&c.is("img")&&c.attr("src",j.placeholder),c.one("appear",function(){if(!this.loaded){if(j.appear){var d=i.length;j.appear.call(b,d,j)}a("").bind("load",function(){var d=c.attr("data-"+j.data_attribute);c.hide(),c.is("img")?c.attr("src",d):c.css("background-image","url('"+d+"')"),c[j.effect](j.effect_speed),b.loaded=!0;var e=a.grep(i,function(a){return!a.loaded});if(i=a(e),j.load){var f=i.length;j.load.call(b,f,j)}}).attr("src",c.attr("data-"+j.data_attribute))}}),0!==j.event.indexOf("scroll")&&c.bind(j.event,function(){b.loaded||c.trigger("appear")})}),e.bind("resize",function(){g()}),/(?:iphone|ipod|ipad).*os 5/gi.test(navigator.appVersion)&&e.bind("pageshow",function(b){b.originalEvent&&b.originalEvent.persisted&&i.each(function(){a(this).trigger("appear")})}),a(c).ready(function(){g()}),this},a.belowthefold=function(c,f){var g;return g=f.container===d||f.container===b?(b.innerHeight?b.innerHeight:e.height())+e.scrollTop():a(f.container).offset().top+a(f.container).height(),g<=a(c).offset().top-f.threshold},a.rightoffold=function(c,f){var g;return g=f.container===d||f.container===b?e.width()+e.scrollLeft():a(f.container).offset().left+a(f.container).width(),g<=a(c).offset().left-f.threshold},a.abovethetop=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollTop():a(f.container).offset().top,g>=a(c).offset().top+f.threshold+a(c).height()},a.leftofbegin=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollLeft():a(f.container).offset().left,g>=a(c).offset().left+f.threshold+a(c).width()},a.inviewport=function(b,c){return!(a.rightoffold(b,c)||a.leftofbegin(b,c)||a.belowthefold(b,c)||a.abovethetop(b,c))},a.extend(a.expr[":"],{"below-the-fold":function(b){return a.belowthefold(b,{threshold:0})},"above-the-top":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-screen":function(b){return a.rightoffold(b,{threshold:0})},"left-of-screen":function(b){return!a.rightoffold(b,{threshold:0})},"in-viewport":function(b){return a.inviewport(b,{threshold:0})},"above-the-fold":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-fold":function(b){return a.rightoffold(b,{threshold:0})},"left-of-fold":function(b){return!a.rightoffold(b,{threshold:0})}})}(jQuery,window,document); \ No newline at end of file diff --git a/prosopopee/themes/material/templates/gallery-index.html b/prosopopee/themes/material/templates/gallery-index.html index 8c106c0..d286f5d 100644 --- a/prosopopee/themes/material/templates/gallery-index.html +++ b/prosopopee/themes/material/templates/gallery-index.html @@ -1,62 +1,85 @@ - - - - + + + + - {% if settings.share %} - - {% endif %} + {% if settings.share %} + + {% endif %} - - - {{ gallery.title }} · {{ settings.title }} - + + + {{ gallery.title }} · {{ settings.title }} + - -
+ +
- {% for section in gallery.sections %} - {% include "sections/" + section.type + ".html" %} - {% endfor %} + {% for section in gallery.sections %} + {% include "sections/" + section.type + ".html" %} + {% endfor %} {% if settings.share %} {% include 'share.html' -%} {% else -%} -
- + + +
{% endif %} - - + + - + -
- {% include 'footer.html'%} +
+ {% include 'footer.html'%} diff --git a/prosopopee/themes/material/templates/sections/pictures-group.html b/prosopopee/themes/material/templates/sections/pictures-group.html index 04b4427..dd84510 100644 --- a/prosopopee/themes/material/templates/sections/pictures-group.html +++ b/prosopopee/themes/material/templates/sections/pictures-group.html @@ -1,33 +1,47 @@ {% if section.background %} -
-{% endif %} -
-
- {% for line in section.images %} -
- {% for image in line %} - {% set caption = image.text %} - {% set image = Image(image) %} - {{ image.copy() }} -
-
-
- - - {% if caption %} -
-
{{ caption }}
-
- {% endif %} -
+
+ {% endif %} +
+
+ {% for line in section.images %} +
+ {% for image in line %} + {% if image.type == "video" %} + {% set video = Video(image) %} + {{ video.copy() }} + {% else %} + {% set caption = image.text %} + {% set image = Image(image) %} + {{ image.copy() }} + {% endif %} +
+
+ {% if video %} +
+ + +
+ {% else %} + + {% endif %}
+ {% endfor %}
{% endfor %}
- {% endfor %}
-
-{% if section.background %} + {% if section.background %}
{% endif %}