annotate pics2/pics_app.py @ 125:d216dd8e63da

pics3: implement thumbs page
author paulo
date Sat, 01 May 2021 01:59:08 -0700
parents d521df55f56c
children
rev   line source
paulo@52 1 import os
paulo@52 2 import re
paulo@52 3 import glob
paulo@52 4 import traceback
paulo@54 5 import datetime
paulo@54 6 import random
paulo@54 7 import urlparse
paulo@52 8
paulo@52 9 import html
paulo@52 10
paulo@82 11 import pinlib
paulo@82 12
paulo@53 13 import logging
paulo@53 14 logging.basicConfig(
paulo@71 15 level=logging.INFO,
paulo@53 16 filename="_LOG",
paulo@53 17 format="%(asctime)s %(levelname)-8s %(message)s",
paulo@53 18 )
paulo@52 19
paulo@52 20
paulo@54 21 def _is_pics_dir(dirpath):
paulo@54 22 return os.path.exists(os.path.join(dirpath, "_picsroot"))
paulo@52 23
paulo@52 24
paulo@54 25 def _get_dir_dt(dirpath):
paulo@54 26 if _is_pics_dir(dirpath):
paulo@54 27 dirpath = os.path.join(dirpath, "_picsroot")
paulo@54 28
paulo@54 29 return datetime.datetime.fromtimestamp(os.stat(dirpath).st_mtime)
paulo@54 30
paulo@54 31
paulo@54 32 def _format_dt(dt):
paulo@54 33 return dt.strftime("%Y-%m-%d")
paulo@53 34
paulo@53 35
paulo@53 36 def _parse_path_info(path_info):
paulo@53 37 ppi = path_info.split(os.sep)
paulo@53 38 if len(ppi) > 1 and ppi[-1] == '':
paulo@53 39 del ppi[-1]
paulo@53 40
paulo@53 41 return ppi
paulo@53 42
paulo@53 43
paulo@53 44 def _numeric_pad_basename(path, maxdigits=20):
paulo@53 45 return os.path.basename(path).zfill(maxdigits)
paulo@53 46
paulo@53 47
paulo@53 48 def _get_images(d):
paulo@57 49 exts = [".jpg", ".webm"]
paulo@57 50
paulo@53 51 thumb_fns = glob.glob(os.path.join(d, "thumbs", "*.jpg"))
paulo@53 52 thumb_fns = sorted(thumb_fns, key=_numeric_pad_basename)
paulo@53 53 logging.debug("thumb_fns = %s" % thumb_fns)
paulo@53 54
paulo@57 55 browse_dir = os.path.join(d, "browse")
paulo@57 56 browse_contents = set(os.listdir(browse_dir))
paulo@57 57 logging.debug("browse_contents = %s" % browse_contents)
paulo@57 58
paulo@57 59 browse_fns = []
paulo@57 60 for i in thumb_fns:
paulo@57 61 i_basename = os.path.splitext(os.path.basename(i))[0]
paulo@57 62 try:
paulo@57 63 for j in exts:
paulo@57 64 browse_fn_basename = i_basename + j
paulo@57 65 if browse_fn_basename in browse_contents:
paulo@57 66 browse_fns.append(os.path.join(browse_dir, browse_fn_basename))
paulo@57 67 raise StopIteration
paulo@57 68 except StopIteration:
paulo@57 69 pass
paulo@57 70 else:
paulo@57 71 raise RuntimeError("Cannot find browse image for %s" % i)
paulo@53 72 logging.debug("browse_fns = %s" % browse_fns)
paulo@53 73
paulo@53 74 return zip(thumb_fns, browse_fns)
paulo@53 75
paulo@52 76
paulo@54 77 class Main:
paulo@54 78 def _get_pics_url(self, dirpath):
paulo@54 79 script_name = self._environ.get("SCRIPT_NAME", '')
paulo@54 80 return os.path.normpath(os.path.join(os.path.dirname(script_name), dirpath))
paulo@54 81
paulo@54 82
paulo@54 83 def _get_app_url(self, dirpath):
paulo@54 84 script_name = self._environ.get("SCRIPT_NAME", '')
paulo@54 85 return os.path.normpath(os.path.join(script_name, dirpath))
paulo@54 86
paulo@54 87
paulo@54 88 def _get_standard_html_doc(self, title):
paulo@54 89 root = html.HTML("html")
paulo@54 90
paulo@96 91 header = root.head
paulo@54 92 header.link(rel="stylesheet", type="text/css", href=self._get_pics_url("index.css"))
paulo@54 93 header.title(title)
paulo@54 94
paulo@54 95 body = root.body
paulo@54 96 body.h1(title)
paulo@54 97
paulo@54 98 return (root, header, body)
paulo@54 99
paulo@54 100
paulo@75 101 def _go_thumbnail_links_to_browse_imgs_html_body(self, body, t, b, a_args={}, img_args={}, lazyload=False):
paulo@54 102 thumb_img_url = self._get_pics_url(t)
paulo@54 103 browse_url = self._get_app_url(b)
paulo@52 104
paulo@56 105 a = body.a(href=browse_url, **a_args)
paulo@75 106 if lazyload:
paulo@75 107 img_args = dict(img_args)
paulo@75 108 img_args["data-src"] = thumb_img_url
paulo@75 109 a.img(**img_args)
paulo@75 110 else:
paulo@75 111 a.img(src=thumb_img_url, **img_args)
paulo@52 112
paulo@54 113 body.text(' ')
paulo@54 114
paulo@57 115
paulo@57 116 def _go_browse_image_html_body(self, body, img):
paulo@57 117 browse_img_url = self._get_pics_url(img)
paulo@57 118
paulo@57 119 ext = os.path.splitext(img)[1]
paulo@57 120 if ext == ".webm":
paulo@57 121 body.video(src=browse_img_url, autoplay="true", loop="true")
paulo@57 122 else:
paulo@57 123 body.img(src=browse_img_url)
paulo@57 124
paulo@54 125
paulo@54 126 def __init__(self, environ):
paulo@54 127 self._environ = environ
paulo@54 128 self._page_func = None
paulo@71 129 self._show_index = False
paulo@54 130
paulo@54 131 #logging.debug("environ = %s" % (sorted(self._environ.items(), key=lambda x: x[0]),))
paulo@54 132 logging.debug("environ['PATH_INFO'] = %s" % self._environ["PATH_INFO"])
paulo@54 133 logging.debug("environ['SCRIPT_NAME'] = %s" % self._environ["SCRIPT_NAME"])
paulo@54 134 logging.debug("environ['QUERY_STRING'] = %s" % self._environ["QUERY_STRING"])
paulo@54 135
paulo@54 136 pi = self._environ["PATH_INFO"]
paulo@54 137 ppi = _parse_path_info(pi)
paulo@54 138 logging.debug("ppi = %s" % ppi)
paulo@54 139
paulo@54 140 if len(ppi) < 1 or ppi[0] != '':
paulo@54 141 raise AssertionError("Parsed path length must start empty: " + pi)
paulo@54 142
paulo@71 143 self._qs = urlparse.parse_qs(self._environ["QUERY_STRING"])
paulo@71 144 logging.debug("self._qs = %s" % self._qs)
paulo@83 145 try:
paulo@83 146 pinlib.PinMan("lahat").check(pinlib.parse_cookies(self._environ))
paulo@83 147 except pinlib.PinFailError:
paulo@83 148 pass
paulo@83 149 else:
paulo@71 150 self._show_index = True
paulo@71 151
paulo@54 152 if len(ppi) >= 2 and _is_pics_dir(ppi[1]):
paulo@54 153 if len(ppi) == 2:
paulo@54 154 self._page_func = self.page_thumbs
paulo@54 155 elif len(ppi) >= 4 and ppi[2] == "browse" and os.path.exists(os.path.join(*ppi)):
paulo@54 156 self._page_func = self.page_browse
paulo@71 157 elif len(ppi) == 1 and self._show_index:
paulo@54 158 self._page_func = self.page_index
paulo@54 159
paulo@54 160 if self._page_func is None:
paulo@54 161 raise RuntimeError("Cannot find path: " + pi)
paulo@54 162
paulo@53 163
paulo@54 164 def page(self):
paulo@54 165 return unicode(self._page_func()).encode("utf-8")
paulo@54 166
paulo@53 167
paulo@54 168 def page_index(self):
paulo@75 169 n = 5 # number of thumbnails to display per dir
paulo@53 170
paulo@54 171 (html_root, html_header, html_body) = self._get_standard_html_doc("Pictures")
paulo@54 172
paulo@75 173 html_header.script('', type="text/javascript", src=self._get_pics_url("lazyload.js"))
paulo@75 174
paulo@54 175 pics_dirs = []
paulo@54 176 for i in os.listdir('.'):
paulo@54 177 if _is_pics_dir(i):
paulo@54 178 pics_dirs.append((i, _get_dir_dt(i)))
paulo@54 179
paulo@54 180 pics_dirs.sort(key=lambda x: x[1], reverse=True)
paulo@75 181
paulo@54 182 for (d, dt) in pics_dirs:
paulo@54 183 html_body.h2.a(d, href=self._get_app_url(d))
paulo@54 184 html_body.h3(_format_dt(dt))
paulo@54 185
paulo@54 186 imgs = _get_images(d)
paulo@54 187 imgs_idx = [(i, img) for (i, img) in enumerate(imgs)]
paulo@54 188
paulo@54 189 sampled_imgs_idx = random.sample(imgs_idx, min(len(imgs_idx), n))
paulo@54 190 sampled_imgs_idx.sort(key=lambda x: x[0])
paulo@54 191
paulo@54 192 html_p = html_body.p
paulo@54 193 for (i, (t, b)) in sampled_imgs_idx:
paulo@75 194 self._go_thumbnail_links_to_browse_imgs_html_body(html_p, t, b, lazyload=True)
paulo@75 195
paulo@54 196 return html_root
paulo@54 197
paulo@54 198
paulo@54 199 def page_thumbs(self):
paulo@54 200 ppi = _parse_path_info(self._environ["PATH_INFO"])
paulo@54 201 d = os.path.join(*ppi)
paulo@54 202 (html_root, html_header, html_body) = self._get_standard_html_doc(d)
paulo@53 203
paulo@54 204 from_img = None
paulo@71 205 if "from" in self._qs:
paulo@71 206 from_img = self._qs["from"][0]
paulo@54 207
paulo@54 208 html_p = html_body.p
paulo@54 209 for (t, b) in _get_images(d):
paulo@54 210 if from_img is not None and b == from_img:
paulo@56 211 self._go_thumbnail_links_to_browse_imgs_html_body(html_p, t, b, img_args={"klass":"sel2", "id":"selected"})
paulo@54 212 else:
paulo@54 213 self._go_thumbnail_links_to_browse_imgs_html_body(html_p, t, b)
paulo@54 214
paulo@71 215 # TODO: fix me
paulo@71 216 #if self._show_index:
paulo@71 217 # html_body.a("(Other pictures)", href=self._get_app_url(''))
paulo@71 218
paulo@54 219 return html_root
paulo@54 220
paulo@54 221
paulo@54 222 def page_browse(self):
paulo@54 223 ppi = _parse_path_info(self._environ["PATH_INFO"])
paulo@54 224 d = os.path.join(*ppi[:2])
paulo@54 225 imgs = _get_images(d)
paulo@54 226 img = os.path.join(*ppi)
paulo@54 227
paulo@54 228 # thumbnail preview ribbon
paulo@54 229 w = 7 # must be odd
paulo@54 230 v = w/2
paulo@54 231 imgs_circ = [None] * w
paulo@54 232 x = None
paulo@54 233 n = len(imgs)
paulo@54 234 for (i, (t, b)) in enumerate(imgs):
paulo@54 235 if b == img:
paulo@54 236 x = i + 1
paulo@54 237 imgs_circ[v] = (t, b)
paulo@54 238 for j in range(1, v + 1):
paulo@54 239 if (i + j) < n: imgs_circ[v + j] = imgs[i + j]
paulo@54 240 if (i - j) >= 0: imgs_circ[v - j] = imgs[i - j]
paulo@54 241
paulo@54 242 break
paulo@54 243
paulo@54 244 if x is None:
paulo@54 245 raise AssertionError
paulo@54 246
paulo@54 247 (html_root, html_header, html_body) = self._get_standard_html_doc(u"%s \u2014 %s of %s" % (d, x, len(imgs)))
paulo@56 248
paulo@56 249 html_header.script('', type="text/javascript", src=self._get_pics_url("np_keys.js"))
paulo@54 250
paulo@57 251 self._go_browse_image_html_body(html_body.p, img)
paulo@54 252
paulo@54 253 logging.debug("imgs_circ = %s" % imgs_circ)
paulo@54 254
paulo@54 255 html_p = html_body.p
paulo@56 256 for (i, img_c) in enumerate(imgs_circ):
paulo@56 257 if img_c is not None:
paulo@56 258 (t, b) = img_c
paulo@56 259 a_args = {}
paulo@56 260 img_args = {}
paulo@54 261 if b == img:
paulo@56 262 a_args = {"id": "up"}
paulo@56 263 img_args = {"klass": "sel"}
paulo@56 264 b = "%s?from=%s#selected" % (d, img)
paulo@56 265 elif i == v + 1:
paulo@56 266 a_args = {"id": "next"}
paulo@56 267 elif i == v - 1:
paulo@56 268 a_args = {"id": "prev"}
paulo@56 269
paulo@56 270 self._go_thumbnail_links_to_browse_imgs_html_body(html_p, t, b, a_args, img_args)
paulo@54 271
paulo@54 272 return html_root
paulo@53 273
paulo@52 274
paulo@52 275 def app(environ, start_response):
paulo@52 276 response_code = "500 Internal Server Error"
paulo@52 277 response_type = "text/plain; charset=UTF-8"
paulo@52 278
paulo@52 279 try:
paulo@54 280 response_body = Main(environ).page()
paulo@52 281 response_code = "200 OK"
paulo@52 282 response_type = "text/html; charset=UTF-8"
paulo@52 283 except:
paulo@52 284 response_body = traceback.format_exc()
paulo@52 285
paulo@52 286 response_headers = [
paulo@52 287 ("Content-Type", response_type),
paulo@52 288 ("Content-Length", str(len(response_body))),
paulo@52 289 ]
paulo@52 290
paulo@52 291 start_response(response_code, response_headers)
paulo@52 292
paulo@52 293 return [response_body]