commit 60ffa66fff083ca33238981815786c6780b3d043 Author: cinnaboot Date: Thu May 6 15:51:14 2021 -0400 initial commit 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 0000000..e9ebc4f Binary files /dev/null and b/gallery/static/default_image.png differ diff --git a/gallery/static/style.css b/gallery/static/style.css new file mode 100644 index 0000000..b8f2d6f --- /dev/null +++ b/gallery/static/style.css @@ -0,0 +1,27 @@ + +.nav li { display: inline; } + +.flex_container { + display: flex; + flex-wrap: wrap; + width: 100%; +} + +.infocard { + margin: 0.5em; + background-color: lightgrey; + font-size: 0.7em; +} +.infocard img { border: 1px solid grey; } +.infocard ul { + list-style-type: none; + margin: 0; + padding: 0; +} +.infocard li { + width: 160px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + diff --git a/gallery/templates/album.j2 b/gallery/templates/album.j2 new file mode 100644 index 0000000..1945fdc --- /dev/null +++ b/gallery/templates/album.j2 @@ -0,0 +1,39 @@ + + + + + + + + + +
+ {% 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 @@ + + + + + + + +
+ +
+ + + 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() +