Mercurial > hg > index.fcgi > www > www-1
changeset 124:9b57b90aea31
initial add for pics3
author | paulo |
---|---|
date | Thu, 25 Mar 2021 00:33:42 -0700 |
parents | b2aebd4994ea |
children | d216dd8e63da |
files | pics3/flask_run_dev.sh pics3/pics_flask_app.py pics3/static/index.css pics3/static/lazyload.js pics3/static/np_keys.js |
diffstat | 5 files changed, 290 insertions(+), 0 deletions(-) [+] |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/pics3/flask_run_dev.sh Thu Mar 25 00:33:42 2021 -0700 1.3 @@ -0,0 +1,8 @@ 1.4 +#!/bin/sh 1.5 + 1.6 +export GOOGLE_APPLICATION_CREDENTIALS=dev.key.json 1.7 +export GCS_BUCKET=dev.pauloang.com 1.8 +export FLASK_APP=pics_flask_app.py 1.9 +export FLASK_ENV=development 1.10 + 1.11 +./bin/flask run
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/pics3/pics_flask_app.py Thu Mar 25 00:33:42 2021 -0700 2.3 @@ -0,0 +1,183 @@ 2.4 +import csv 2.5 +import datetime 2.6 +import random 2.7 +import os 2.8 + 2.9 +import flask 2.10 +import google.cloud.storage 2.11 +from html3.html3 import HTML 2.12 + 2.13 +app = flask.Flask(__name__) 2.14 + 2.15 +GCS_CLIENT = google.cloud.storage.Client() 2.16 +GCS_BUCKET = GCS_CLIENT.get_bucket(os.environ.get("GCS_BUCKET")) 2.17 + 2.18 + 2.19 +class PicsDialect(csv.Dialect): 2.20 + delimiter = '\t' 2.21 + quoting = csv.QUOTE_NONE 2.22 + lineterminator = '\n' 2.23 + 2.24 +PICSDIALECT = PicsDialect() 2.25 + 2.26 + 2.27 +def _parse_dt(dts): 2.28 + return datetime.datetime.strptime(dts, "%Y%m%d") 2.29 + 2.30 + 2.31 +def _format_dt(dt): 2.32 + return dt.strftime("%Y-%m-%d") 2.33 + 2.34 + 2.35 +def _numeric_pad_basename(path, maxdigits=20): 2.36 + return os.path.basename(path).zfill(maxdigits) 2.37 + 2.38 + 2.39 +def _get_images(d): 2.40 + exts = (".jpg", ".webm") 2.41 + thumb_dir = f"pics/{d}/thumbs" 2.42 + browse_dir = f"pics/{d}/browse" 2.43 + 2.44 + thumb_fns = [i.name for i in GCS_CLIENT.list_blobs(GCS_BUCKET, prefix=thumb_dir) if i.name.endswith(exts)] 2.45 + thumb_fns = sorted(thumb_fns, key=_numeric_pad_basename) 2.46 + 2.47 + browse_contents = set(i.name for i in GCS_CLIENT.list_blobs(GCS_BUCKET, prefix=browse_dir) if i.name.endswith(exts)) 2.48 + browse_fns = [] 2.49 + for i in thumb_fns: 2.50 + i_basename = os.path.splitext(os.path.basename(i))[0] 2.51 + try: 2.52 + for j in exts: 2.53 + browse_fn = browse_dir + "/" + i_basename + j 2.54 + if browse_fn in browse_contents: 2.55 + browse_fns.append(browse_fn) 2.56 + raise StopIteration 2.57 + except StopIteration: 2.58 + pass 2.59 + else: 2.60 + raise RuntimeError(f"Cannot find browse image for {i}") 2.61 + 2.62 + return zip(thumb_fns, browse_fns) 2.63 + 2.64 + 2.65 +def _get_standard_html_doc(title): 2.66 + root = HTML("html") 2.67 + 2.68 + header = root.head 2.69 + header.link(rel="stylesheet", type="text/css", href=flask.url_for("static", filename="index.css")) 2.70 + header.title(title) 2.71 + 2.72 + body = root.body 2.73 + body.h1(title) 2.74 + 2.75 + return (root, header, body) 2.76 + 2.77 + 2.78 +def _go_thumbnail_links_to_browse_imgs_html_body(body, d, t, b, a_args={}, img_args={}, lazyload=False): 2.79 + thumb_img_url = GCS_BUCKET.get_blob(t).public_url 2.80 + browse_url = flask.url_for("browse", d=d, img=os.path.basename(b)) 2.81 + 2.82 + a = body.a(href=browse_url, **a_args) 2.83 + if lazyload: 2.84 + img_args = dict(img_args) 2.85 + img_args["data-src"] = thumb_img_url 2.86 + a.img(**img_args) 2.87 + else: 2.88 + a.img(src=thumb_img_url, **img_args) 2.89 + 2.90 + body.text(" ") 2.91 + 2.92 + 2.93 +@app.route("/") 2.94 +def index(): 2.95 + n = 5 # number of thumbnails to display per dir 2.96 + 2.97 + (root, header, body) = _get_standard_html_doc("Pictures") 2.98 + header.script('', type="text/javascript", src=flask.url_for("static", filename="lazyload.js")) 2.99 + 2.100 + #body.pre(str(list(GCS_CLIENT.list_blobs(GCS_BUCKET)))) 2.101 + 2.102 + pics_dirs = [] 2.103 + pics_dirs_index_blob = GCS_BUCKET.get_blob("pics/index.tsv") 2.104 + if pics_dirs_index_blob: 2.105 + pics_dirs_index_strlist = pics_dirs_index_blob.download_as_text().splitlines() 2.106 + pics_dirs_index_reader = csv.reader(pics_dirs_index_strlist, PICSDIALECT) 2.107 + pics_dirs = sorted(pics_dirs_index_reader, key=lambda x: x[0]) 2.108 + 2.109 + for (dts, d) in pics_dirs: 2.110 + dt = _parse_dt(dts) 2.111 + body.h2(d) 2.112 + body.h3(_format_dt(dt)) 2.113 + 2.114 + #body.pre(str(list(_get_images(d)))) 2.115 + 2.116 + imgs = _get_images(d) 2.117 + imgs_idx = [(i, img) for (i, img) in enumerate(imgs)] 2.118 + 2.119 + sampled_imgs_idx = random.sample(imgs_idx, min(len(imgs_idx), n)) 2.120 + sampled_imgs_idx.sort(key=lambda x: x[0]) 2.121 + 2.122 + p = body.p 2.123 + for (i, (t, b)) in sampled_imgs_idx: 2.124 + _go_thumbnail_links_to_browse_imgs_html_body(p, d, t, b, lazyload=True) 2.125 + 2.126 + return str(root).encode("utf-8") 2.127 + 2.128 + 2.129 +@app.route("/<d>/browse/<img>") 2.130 +def browse(d, img): 2.131 + browse_img_blob = GCS_BUCKET.get_blob(f"pics/{d}/browse/{img}") 2.132 + if not browse_img_blob: 2.133 + flask.abort(404) 2.134 + 2.135 + imgs = list(_get_images(d)) 2.136 + 2.137 + # thumbnail preview ribbon 2.138 + w = 7 # must be odd 2.139 + v = int(w/2) 2.140 + imgs_circ = [None] * w 2.141 + x = None 2.142 + n = len(imgs) 2.143 + for (i, (t, b)) in enumerate(imgs): 2.144 + if os.path.basename(b) == img: 2.145 + x = i + 1 2.146 + imgs_circ[v] = (t, b) 2.147 + for j in range(1, v + 1): 2.148 + if (i + j) < n: imgs_circ[v + j] = imgs[i + j] 2.149 + if (i - j) >= 0: imgs_circ[v - j] = imgs[i - j] 2.150 + break 2.151 + 2.152 + if x is None: 2.153 + raise AssertionError 2.154 + 2.155 + (root, header, body) = _get_standard_html_doc(f"{d} \u2014 {x} of {n}") 2.156 + header.script('', type="text/javascript", src=flask.url_for("static", filename="np_keys.js")) 2.157 + 2.158 + browse_img_url = browse_img_blob.public_url 2.159 + ext = os.path.splitext(img)[1] 2.160 + p = body.p 2.161 + if ext == ".webm": 2.162 + p.video(src=browse_img_url, autoplay="true", loop="true") 2.163 + else: 2.164 + p.img(src=browse_img_url) 2.165 + 2.166 + p = body.p 2.167 + for (i, img_c) in enumerate(imgs_circ): 2.168 + if img_c is not None: 2.169 + (t, b) = img_c 2.170 + a_args = {} 2.171 + img_args = {} 2.172 + # FIXME 2.173 + #if b == img: 2.174 + # a_args = {"id": "up"} 2.175 + # img_args = {"klass": "sel"} 2.176 + # b = f"{d}?from={img}#selected" 2.177 + if False: 2.178 + pass 2.179 + elif i == v + 1: 2.180 + a_args = {"id": "next"} 2.181 + elif i == v - 1: 2.182 + a_args = {"id": "prev"} 2.183 + 2.184 + _go_thumbnail_links_to_browse_imgs_html_body(p, d, t, b, a_args, img_args) 2.185 + 2.186 + return str(root).encode("utf-8")
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/pics3/static/index.css Thu Mar 25 00:33:42 2021 -0700 3.3 @@ -0,0 +1,34 @@ 3.4 +body 3.5 +{ 3.6 + background-color: #111; 3.7 + color: #ccc; 3.8 +} 3.9 + 3.10 +a:link 3.11 +{ 3.12 + color: #831; 3.13 +} 3.14 + 3.15 +a:visited 3.16 +{ 3.17 + color: gray; 3.18 +} 3.19 + 3.20 +img 3.21 +{ 3.22 + border-width: 0px; 3.23 +} 3.24 + 3.25 +img.sel 3.26 +{ 3.27 + padding: 2px; 3.28 + border-width: 4px; 3.29 + border-style: solid; 3.30 +} 3.31 + 3.32 +img.sel2 3.33 +{ 3.34 + padding: 2px; 3.35 + border-width: 2px; 3.36 + border-style: dashed; 3.37 +}
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 4.2 +++ b/pics3/static/lazyload.js Thu Mar 25 00:33:42 2021 -0700 4.3 @@ -0,0 +1,42 @@ 4.4 +window.addEventListener('load', lazyLoadImages); 4.5 +window.addEventListener('DOMContentLoaded', lazyLoadImages); 4.6 +window.addEventListener('resize', lazyLoadImages); 4.7 +window.addEventListener('scroll', lazyLoadImages); 4.8 + 4.9 +function lazyLoadImages() { 4.10 + var images = document.getElementsByTagName('img'); 4.11 + var loaded = 0; 4.12 + 4.13 + for (var i = 0; i < images.length; i++) { 4.14 + var img = images[i]; 4.15 + if (img.hasAttribute('data-src')) { 4.16 + if (isElementInViewport(img)) { 4.17 + img.setAttribute('src', img.getAttribute('data-src')); 4.18 + img.removeAttribute('data-src'); 4.19 + } 4.20 + } else { 4.21 + loaded++; 4.22 + } 4.23 + } 4.24 + 4.25 + //console.log('Loaded images:' + loaded + '/' + images.length); 4.26 + 4.27 + if (loaded == images.length) { 4.28 + //console.log('Loaded all images.'); 4.29 + window.removeEventListener('load', lazyLoadImages); 4.30 + window.removeEventListener('DOMContentLoaded', lazyLoadImages); 4.31 + window.removeEventListener('resize', lazyLoadImages); 4.32 + window.removeEventListener('scroll', lazyLoadImages); 4.33 + } 4.34 +} 4.35 + 4.36 +function isElementInViewport(el) { 4.37 + var rect = el.getBoundingClientRect(); 4.38 + 4.39 + return ( 4.40 + rect.top >= 0 && 4.41 + rect.left >= 0 && 4.42 + rect.top <= window.innerHeight && 4.43 + rect.left <= window.innerWidth 4.44 + ); 4.45 +}
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/pics3/static/np_keys.js Thu Mar 25 00:33:42 2021 -0700 5.3 @@ -0,0 +1,23 @@ 5.4 +function getKeypress(e) { 5.5 + c = null 5.6 + 5.7 + if (e.which == null) 5.8 + c = String.fromCharCode(e.keyCode); // IE 5.9 + else if (e.which != 0 && e.charCode != 0) 5.10 + c = String.fromCharCode(e.which); // All others 5.11 + 5.12 + if (c != null) { 5.13 + if (c == 'n') 5.14 + goHref('next'); 5.15 + else if (c == 'p') 5.16 + goHref('prev'); 5.17 + else if (c == 'u') 5.18 + goHref('up'); 5.19 + } 5.20 +} 5.21 + 5.22 +function goHref(id) { 5.23 + window.location.href = document.getElementById(id).href; 5.24 +} 5.25 + 5.26 +document.onkeypress = getKeypress