From 60ffa66fff083ca33238981815786c6780b3d043 Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Thu, 6 May 2021 15:51:14 -0400 Subject: [PATCH] initial commit --- .gitignore | 9 +++ MANIFEST.in | 4 ++ gallery/__init__.py | 22 +++++++ gallery/static/default_image.png | Bin 0 -> 2168 bytes gallery/static/style.css | 27 ++++++++ gallery/templates/album.j2 | 39 +++++++++++ gallery/templates/index.j2 | 20 ++++++ gallery/views/default_routes.py | 110 +++++++++++++++++++++++++++++++ setup.py | 11 ++++ wsgi.py | 5 ++ 10 files changed, 247 insertions(+) create mode 100644 .gitignore create mode 100644 MANIFEST.in create mode 100644 gallery/__init__.py create mode 100644 gallery/static/default_image.png create mode 100644 gallery/static/style.css create mode 100644 gallery/templates/album.j2 create mode 100644 gallery/templates/index.j2 create mode 100644 gallery/views/default_routes.py create mode 100644 setup.py create mode 100644 wsgi.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..903e63d --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +tags +*.swp +instance/ +*.egg-info/ +__pycache__/ +venv/ +build/ +dist/ + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..2a65d5e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ + +recursive-include gallery/templates * +recursive-include gallery/static * + diff --git a/gallery/__init__.py b/gallery/__init__.py new file mode 100644 index 0000000..0c59779 --- /dev/null +++ b/gallery/__init__.py @@ -0,0 +1,22 @@ + +import os + +from flask import Flask + +from .views import default_routes + + +def create_app(): + app = Flask(__name__, instance_relative_config=True) + + # NOTE: ensure intance folders exist + try: os.makedirs(app.instance_path) + except OSError: pass + try: os.makedirs(os.path.join(app.instance_path, 'images')) + except OSError: pass + try: os.makedirs(os.path.join(app.instance_path, 'thumbnails')) + except OSError: pass + + app.register_blueprint(default_routes.bp) + return app + diff --git a/gallery/static/default_image.png b/gallery/static/default_image.png new file mode 100644 index 0000000000000000000000000000000000000000..e9ebc4f1e61f3f227166d64a5cfc43c9dbdeb414 GIT binary patch literal 2168 zcmV-;2#5EHP)003GD1^@s6KgL~B0004nX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKp2MKww6k%h}c2JAwzYtAS&XhRVYG*P%E_RU~=gnG-*gu zTpR`0f`dPcRRb`7 zkz)Z>sE`~#_#gc4ty!3yaFZelAoyb2A0t3$7iiXP`}^3on2C8-PYti;Bp5Tc+w?9a-;w)f1v=ppV2qvfc{$`yyo_;b&k^qAVa-M-2exN zz-W=O*FE0d-PyN)Yg+yL0SBmZeD3o0+W-In24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2jv9?5g#yv1gNF}000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}000J0Nkl22e=<-gEK`vu0Nh%qjI080sWH#D@7nbl1tjCs zY@u+Sh`gx4l5=SA8%?F&1#sOI=+0qd?aGDa^5>0`ziDLo4tI5BM^mZ&Q$#)hXt%~3s1ynhas*8J zdatvy9mebfaPgG9F{)2xg5X&Sm_Uwpch9n8V+R0SHl=Tls+S1@pHpyxP(W&h8+w)r%-#5;h%c)?6s6&&RCWGhyw^6fg-3 z-fDFtfD7s~vttOrsXBH2j!L1hlmfQ>)!Gk%z^H*R4x8ojkc- zMV`d?i5XYcW_^DL1&kTT^7+(&Lkr4*)X0|Akn46M*B6eYk$GSe#1%<2TF~9ZT%b`2NE# z1v2IG4^S-vur8NkRhLu1n4y5nv^Az84@7aAIU5B13T$?|7LgScFh&e?c3ui#mP3hc zxl-xt$leU9F94`IlvsK!pKqmrvAIHXjpN2+Yn0t-4}zZnyy;M)Ww^EVatas|ED&Bm z)o-$;(!uE01iHtid6Tv66fiy;kgFV;zgL$vsWBfoy{SYNP{8;wFSNYI9IQu&a;4G< z0HKfG8*67!!1!=s=u5*EMF6IS z-US1{QouMY|HcmvcvCO+)G}!JPxg$p-*O5jp|Pq?-}|kl(-e?|b9&KfUVXEv)eb0-iJ;uxobUHhKr%7; z3WZKs`vHL25tKV&QmL*a=0pMOvEe8d*F(eykdC07RiJC(&Yj1Tm{p3pM27-0;CU~p z>SK|WRJ}JB1bs=(tclu0WJ*tusm!1Mx&Ze_R#WxSToAmLYceD_^MIz z<0m0*Bi}6w^uyt7-`{u}BO`ZbdwWkcQic>TB;T!FJ0n%CekTs!y8DX}0oG@He^Vpo z$SF7^)2h{H;4a_y)|ie){=YbmfFT(4yen#|Upl^lx3x~6{>Z#7Th2CemK*^?V8|hNO52zuN5Dx_$>(Pxo&Fi&escGB3oLK<{g0U>N5D{MbMu`NPCg6O741Pl z`ThnEBTRnESQ4(GyqFDwekMo(-2@a_9HQCT=HcNDOpgLOakRU;CCuScQGLIyxA!Cy zq<~J0sm>3zZUhc6JqqYd)97d@0U`1O)1!dSSdno5cBFcO=}|!U4Tv$Jo|~%3A54z| zx}zFOKx6F~!~1XeAbK()rBeM>YcH8>yVKLBa|$Me5JCtcgpjEK&jU`7N}cwH(D{zJ>{u@4sCKQlRKtd=Wp@4)?Ktcftp@4(}5<&q9 u1tf$55(-EN1tf$JLI@#*5JCuvAAbTK{Ozfm?^J&P0000 + + + + + + + + +
+ {% for image in images %} +
+ + + +
    +
  • name: {{ image.name }}
  • +
  • date: ...
  • +
  • dimensions: ...
  • +
  • filesize: ...
  • +
+
+ {% endfor %} +
+ + + diff --git a/gallery/templates/index.j2 b/gallery/templates/index.j2 new file mode 100644 index 0000000..32e31d9 --- /dev/null +++ b/gallery/templates/index.j2 @@ -0,0 +1,20 @@ + + + + + + + +
+
    + {% for album in albums %} +
  • {{ album }}
  • + {% endfor %} +
+
+ + + diff --git a/gallery/views/default_routes.py b/gallery/views/default_routes.py new file mode 100644 index 0000000..aca60e5 --- /dev/null +++ b/gallery/views/default_routes.py @@ -0,0 +1,110 @@ + +import os +import re + +from PIL import Image + +from flask import Blueprint, current_app, redirect, render_template, url_for +from flask import send_from_directory, Response + + +bp = Blueprint('default_routes', __name__) +VALID_CHARS = re.compile(r"^[a-zA-Z0-9_\-\.]+$") + +# +# Routes +# + +@bp.route('/') +def index() -> Response: + album_dir = os.path.join(current_app.instance_path, 'images') + album_dirs = [] + + if os.path.isdir(album_dir): + entries = os.listdir(album_dir) + for e in entries: + if os.path.isdir(os.path.join(album_dir, e)): + album_dirs.append(e) + + return render_template('index.j2', albums = sorted(album_dirs)) + +@bp.route('/album/') +def album(path:str = '') -> Response: + image_dir = joinPath(current_app.instance_path, 'images') + album_dir = joinPath(image_dir, path) + images = [] + + if not validateDir(image_dir, path): + return redirect(url_for('.index')) + + for entry in os.scandir(album_dir): + if entry.is_file(): + createThumbnail(entry, path) + images.append(getImageDetails(entry)) + + return render_template('album.j2', + img_root = f'/image/{path}/', + album = path, + images = images + ) + +@bp.route('/image//') +def image(path:str, img:str) -> Response: + base_dir = os.path.join(current_app.instance_path, 'images') + album_dir = os.path.join(base_dir, os.path.basename(path)) + return send_from_directory(album_dir, img) + +@bp.route('/thumbs//') +def thumbs(path:str, img:str) -> Response: + base_dir = os.path.join(current_app.instance_path, 'thumbnails') + album_dir = os.path.join(base_dir, os.path.basename(path)) + return send_from_directory(album_dir, img) + + +# +# Helpers +# + +def joinPath(base:os.PathLike, path:str) -> os.PathLike: + return os.path.join(base, os.path.basename(path)) + +def validateDir(base:os.PathLike, path:str) -> bool: + if VALID_CHARS.match(path): + path = joinPath(base, path) + + if os.path.exists(path) and os.path.isdir(path): + return True + + return False + +def validateFile(base:os.PathLike, path:str) -> bool: + if VALID_CHARS.match(path) and os.path.exists(joinPath(base, path)): + return True + + return False + +def getImageDetails(entry:os.PathLike) -> dict: + return { + 'name': entry.name, + 'date': '...', + 'dims': '...', + 'size': '...' + } + +def createThumbnail(image:os.DirEntry, album:str) -> None: + thumb_path = joinPath(current_app.instance_path, 'thumbnails') + thumb_path = joinPath(thumb_path, album) + + # NOTE: check for existing thumbnail + if validateFile(thumb_path, image.name): + return + + # NOTE: ensure thumbnail album directory exists + try: os.makedirs(thumb_path) + except OSError: pass + + with Image.open(image.path) as im: + im.thumbnail([160, 160]) + im.save(joinPath(thumb_path, image.name), "JPEG") + + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..312a98b --- /dev/null +++ b/setup.py @@ -0,0 +1,11 @@ + +from setuptools import setup + +setup( + name='gallery', + version='0.1', + packages=['gallery'], + include_package_data=True, + install_requires=['flask', 'pillow'] +) + diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..d443f0c --- /dev/null +++ b/wsgi.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 + +import gallery +app = gallery.create_app() +