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