annotate pics3/pics_flask_app.py @ 130:06f97e38e1b2

pics3: add pin support
author paulo
date Thu, 20 Jan 2022 00:40:14 -0800
parents d216dd8e63da
children 41a6e2d99f68
rev   line source
paulo@124 1 import csv
paulo@124 2 import datetime
paulo@124 3 import random
paulo@124 4 import os
paulo@124 5
paulo@124 6 import flask
paulo@124 7 import google.cloud.storage
paulo@124 8 from html3.html3 import HTML
paulo@124 9
paulo@124 10 app = flask.Flask(__name__)
paulo@124 11
paulo@124 12 GCS_CLIENT = google.cloud.storage.Client()
paulo@124 13 GCS_BUCKET = GCS_CLIENT.get_bucket(os.environ.get("GCS_BUCKET"))
paulo@130 14 PIN = os.environ.get("PIN")
paulo@130 15
paulo@130 16
paulo@130 17 class PinFailError(Exception):
paulo@130 18 def __str__(self):
paulo@130 19 return "PIN FAIL!"
paulo@130 20
paulo@130 21 class PinSetupError(Exception):
paulo@130 22 def __str__(self):
paulo@130 23 return "PIN SETUP ERROR!"
paulo@124 24
paulo@124 25
paulo@124 26 class PicsDialect(csv.Dialect):
paulo@124 27 delimiter = '\t'
paulo@124 28 quoting = csv.QUOTE_NONE
paulo@124 29 lineterminator = '\n'
paulo@124 30
paulo@124 31 PICSDIALECT = PicsDialect()
paulo@124 32
paulo@124 33
paulo@124 34 def _parse_dt(dts):
paulo@124 35 return datetime.datetime.strptime(dts, "%Y%m%d")
paulo@124 36
paulo@124 37
paulo@124 38 def _format_dt(dt):
paulo@124 39 return dt.strftime("%Y-%m-%d")
paulo@124 40
paulo@124 41
paulo@124 42 def _numeric_pad_basename(path, maxdigits=20):
paulo@124 43 return os.path.basename(path).zfill(maxdigits)
paulo@124 44
paulo@124 45
paulo@124 46 def _get_images(d):
paulo@124 47 exts = (".jpg", ".webm")
paulo@124 48 thumb_dir = f"pics/{d}/thumbs"
paulo@124 49 browse_dir = f"pics/{d}/browse"
paulo@124 50
paulo@124 51 thumb_fns = [i.name for i in GCS_CLIENT.list_blobs(GCS_BUCKET, prefix=thumb_dir) if i.name.endswith(exts)]
paulo@124 52 thumb_fns = sorted(thumb_fns, key=_numeric_pad_basename)
paulo@124 53
paulo@124 54 browse_contents = set(i.name for i in GCS_CLIENT.list_blobs(GCS_BUCKET, prefix=browse_dir) if i.name.endswith(exts))
paulo@124 55 browse_fns = []
paulo@124 56 for i in thumb_fns:
paulo@124 57 i_basename = os.path.splitext(os.path.basename(i))[0]
paulo@124 58 try:
paulo@124 59 for j in exts:
paulo@124 60 browse_fn = browse_dir + "/" + i_basename + j
paulo@124 61 if browse_fn in browse_contents:
paulo@124 62 browse_fns.append(browse_fn)
paulo@124 63 raise StopIteration
paulo@124 64 except StopIteration:
paulo@124 65 pass
paulo@124 66 else:
paulo@124 67 raise RuntimeError(f"Cannot find browse image for {i}")
paulo@124 68
paulo@124 69 return zip(thumb_fns, browse_fns)
paulo@124 70
paulo@124 71
paulo@124 72 def _get_standard_html_doc(title):
paulo@124 73 root = HTML("html")
paulo@124 74
paulo@124 75 header = root.head
paulo@124 76 header.link(rel="stylesheet", type="text/css", href=flask.url_for("static", filename="index.css"))
paulo@124 77 header.title(title)
paulo@124 78
paulo@124 79 body = root.body
paulo@124 80 body.h1(title)
paulo@124 81
paulo@124 82 return (root, header, body)
paulo@124 83
paulo@124 84
paulo@124 85 def _go_thumbnail_links_to_browse_imgs_html_body(body, d, t, b, a_args={}, img_args={}, lazyload=False):
paulo@124 86 thumb_img_url = GCS_BUCKET.get_blob(t).public_url
paulo@124 87 browse_url = flask.url_for("browse", d=d, img=os.path.basename(b))
paulo@124 88
paulo@124 89 a = body.a(href=browse_url, **a_args)
paulo@124 90 if lazyload:
paulo@124 91 img_args = dict(img_args)
paulo@124 92 img_args["data-src"] = thumb_img_url
paulo@124 93 a.img(**img_args)
paulo@124 94 else:
paulo@124 95 a.img(src=thumb_img_url, **img_args)
paulo@124 96
paulo@124 97 body.text(" ")
paulo@124 98
paulo@124 99
paulo@125 100 def _go_thumbnail_links_to_thumbs_html_body(body, d, t, a_args={}, img_args={}):
paulo@125 101 thumb_img_url = GCS_BUCKET.get_blob(t).public_url
paulo@125 102 thumbs_url_args = {"from": os.path.basename(t), "_anchor": "selected"}
paulo@125 103 thumbs_url = flask.url_for("thumbs", d=d, **thumbs_url_args)
paulo@125 104
paulo@125 105 a = body.a(href=thumbs_url, **a_args)
paulo@125 106 a.img(src=thumb_img_url, **img_args)
paulo@125 107
paulo@125 108 body.text(" ")
paulo@125 109
paulo@125 110
paulo@124 111 @app.route("/")
paulo@124 112 def index():
paulo@124 113 n = 5 # number of thumbnails to display per dir
paulo@124 114
paulo@124 115 (root, header, body) = _get_standard_html_doc("Pictures")
paulo@124 116 header.script('', type="text/javascript", src=flask.url_for("static", filename="lazyload.js"))
paulo@124 117
paulo@130 118 if not PIN:
paulo@130 119 raise PinSetupError
paulo@130 120 elif flask.request.cookies.get("lahat") != PIN:
paulo@130 121 raise PinFailError
paulo@130 122
paulo@124 123 pics_dirs = []
paulo@124 124 pics_dirs_index_blob = GCS_BUCKET.get_blob("pics/index.tsv")
paulo@124 125 if pics_dirs_index_blob:
paulo@124 126 pics_dirs_index_strlist = pics_dirs_index_blob.download_as_text().splitlines()
paulo@124 127 pics_dirs_index_reader = csv.reader(pics_dirs_index_strlist, PICSDIALECT)
paulo@125 128 pics_dirs = sorted(pics_dirs_index_reader, key=lambda x: x[0], reverse=True)
paulo@124 129
paulo@124 130 for (dts, d) in pics_dirs:
paulo@124 131 dt = _parse_dt(dts)
paulo@125 132 body.h2.a(d, href=flask.url_for("thumbs", d=d))
paulo@124 133 body.h3(_format_dt(dt))
paulo@124 134
paulo@124 135 imgs = _get_images(d)
paulo@124 136 imgs_idx = [(i, img) for (i, img) in enumerate(imgs)]
paulo@124 137
paulo@124 138 sampled_imgs_idx = random.sample(imgs_idx, min(len(imgs_idx), n))
paulo@124 139 sampled_imgs_idx.sort(key=lambda x: x[0])
paulo@124 140
paulo@124 141 p = body.p
paulo@124 142 for (i, (t, b)) in sampled_imgs_idx:
paulo@124 143 _go_thumbnail_links_to_browse_imgs_html_body(p, d, t, b, lazyload=True)
paulo@124 144
paulo@124 145 return str(root).encode("utf-8")
paulo@124 146
paulo@124 147
paulo@124 148 @app.route("/<d>/browse/<img>")
paulo@124 149 def browse(d, img):
paulo@124 150 browse_img_blob = GCS_BUCKET.get_blob(f"pics/{d}/browse/{img}")
paulo@124 151 if not browse_img_blob:
paulo@124 152 flask.abort(404)
paulo@124 153
paulo@124 154 imgs = list(_get_images(d))
paulo@124 155
paulo@124 156 # thumbnail preview ribbon
paulo@124 157 w = 7 # must be odd
paulo@124 158 v = int(w/2)
paulo@124 159 imgs_circ = [None] * w
paulo@124 160 x = None
paulo@124 161 n = len(imgs)
paulo@124 162 for (i, (t, b)) in enumerate(imgs):
paulo@124 163 if os.path.basename(b) == img:
paulo@124 164 x = i + 1
paulo@124 165 imgs_circ[v] = (t, b)
paulo@124 166 for j in range(1, v + 1):
paulo@124 167 if (i + j) < n: imgs_circ[v + j] = imgs[i + j]
paulo@124 168 if (i - j) >= 0: imgs_circ[v - j] = imgs[i - j]
paulo@124 169 break
paulo@124 170
paulo@124 171 if x is None:
paulo@124 172 raise AssertionError
paulo@124 173
paulo@124 174 (root, header, body) = _get_standard_html_doc(f"{d} \u2014 {x} of {n}")
paulo@124 175 header.script('', type="text/javascript", src=flask.url_for("static", filename="np_keys.js"))
paulo@124 176
paulo@124 177 browse_img_url = browse_img_blob.public_url
paulo@124 178 ext = os.path.splitext(img)[1]
paulo@124 179 p = body.p
paulo@124 180 if ext == ".webm":
paulo@124 181 p.video(src=browse_img_url, autoplay="true", loop="true")
paulo@124 182 else:
paulo@124 183 p.img(src=browse_img_url)
paulo@124 184
paulo@124 185 p = body.p
paulo@124 186 for (i, img_c) in enumerate(imgs_circ):
paulo@124 187 if img_c is not None:
paulo@124 188 (t, b) = img_c
paulo@124 189 a_args = {}
paulo@124 190 img_args = {}
paulo@125 191 if os.path.basename(b) == img:
paulo@125 192 a_args = {"id": "up"}
paulo@125 193 img_args = {"klass": "sel"}
paulo@125 194 b = None
paulo@124 195 elif i == v + 1:
paulo@124 196 a_args = {"id": "next"}
paulo@124 197 elif i == v - 1:
paulo@124 198 a_args = {"id": "prev"}
paulo@124 199
paulo@125 200 if b is None:
paulo@125 201 _go_thumbnail_links_to_thumbs_html_body(p, d, t, a_args, img_args)
paulo@125 202 else:
paulo@125 203 _go_thumbnail_links_to_browse_imgs_html_body(p, d, t, b, a_args, img_args)
paulo@124 204
paulo@124 205 return str(root).encode("utf-8")
paulo@125 206
paulo@125 207
paulo@125 208 @app.route("/<d>")
paulo@125 209 def thumbs(d):
paulo@125 210 args = flask.request.args
paulo@125 211 print(f"args = {args}")
paulo@125 212
paulo@125 213 from_img = args.get("from")
paulo@125 214
paulo@125 215 imgs = list(_get_images(d))
paulo@125 216 (root, header, body) = _get_standard_html_doc(d)
paulo@125 217
paulo@125 218 p = body.p
paulo@125 219 for (t, b) in imgs:
paulo@125 220 img_args = {}
paulo@125 221 if os.path.basename(b) == from_img:
paulo@125 222 img_args={"klass": "sel2", "id":"selected"}
paulo@125 223 _go_thumbnail_links_to_browse_imgs_html_body(p, d, t, b, img_args=img_args)
paulo@125 224
paulo@125 225 return str(root).encode("utf-8")