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