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")
|