a static photogallery, made with flask/python, javascript and json
Go to file
Markus Pawlata 37371fc962 compatibility fix for the fake iterator in ProcessWrapper 2019-07-14 02:08:57 +02:00
scanner compatibility fix for the fake iterator in ProcessWrapper 2019-07-14 02:08:57 +02:00
web - seriously refactored so imports hurt less 2019-07-14 01:20:35 +02:00
.gitignore pep8'd what I could, including tabs to spaces 2018-11-30 01:28:24 +01:00
COPYING Add readme 2014-03-12 18:23:41 -06:00
README.md Updated readme, changes to sample to match, some smaller fixes 2018-12-10 23:51:38 +01:00
deployment-config.mk.sample Restored local js/css processing, removed superfluous .gitignore files, removed htacces-files, moved sample config to sample files 2018-11-30 00:12:11 +01:00


subPhotoFloat A Photofloat fork

by Jason A. Donenfeld (Jason@zx2c4.com)
with some changes by Markus Pawlata (markus@derdritte.net) and other collaborators.


subPhotoFloat is an open source web photo gallery aimed at sleekness and speed. It keeps with a minimalist philosophy, preferring to work over directory structures rather than bloated photo database management software (there are good options available for that, if you want it). Everything it generates is static, which means it's extremely fast.

Check out a demo!

How It Works

subPhotoFloat consists of two main segments a Python script and a JavaScript application.

The Python script scans a directory tree of images, whereby each directory constitutes an album. It then populates a second folder, known as the cache folder with statically generated JSON files and thumbnails. The scanner extracts metadata from EXIF tags in JPEG photos. subPhotoFloat is smart about file and directory modification time, so you are free to run the scanner script as many times as you want, and it will be very fast if there are few or zero changes since the last time you ran it. Also a part of the Python script is a small flask webapp which allows you to have authentication for certain albums/images and can start the scanner.

The JavaScript application consists of a single index.html file with a single scripts.min.js and a single styles.min.css. It fetches the statically generated JSON files and thumbnails on the fly from the cache folder to create a speedy interface. Features include:

  • Separate album view and photo view
  • Album metadata pre-fetching
  • Photo pre-loading
  • Smooth up and down scaling
  • Mouse-wheel support
  • Swipe support (on mobile)
  • Metadata display
  • Consistent hash url format
  • Linkable states via ajax urls
  • Link to original images (can be turned off)
  • Optional Google Analytics integration
  • Optional server-side authentication support
  • A thousand other tweaks here and there...

It is, essentially, a very slick and fast, fairly minimal but still well-featured photo gallery app.


  • python >= 2.6
  • pillow >= 5.3.0
  • nginx (or any webserver, really)


  • flask >= 0.11 (for authentication)
  • flask-login >= 0.4.1 (for authentication)
  • virtualenv (this is nice, believe me)
  • ffmpeg (for video conversion)


Download the source code from the git repository

$ git clone https://derdritte.net/gitea/markus/photofloat
$ cd photofloat

Change or delete the Google Analytics ID tracker

To delete:

$ rm web/js/999-googletracker.js

To change:

$ vim web/js/999-googletracker.js

Modify the part that says UA-XXXXXX-X and put your own in there.

$ vim web/index.html

Build the web page

This simply runs all the javascript through Google Closure Compiler and all the CSS through YUI Compressor to minify and concatenate everything. Be sure you have java installed.

$ cd web
$ make

Generate the albums

Now that we're in the web directory, let's make a folder for cache and a folder for the pictures:

$ mkdir albums
$ mkdir cache

When you're done, fill albums with photos and directories of photos. You can also use symlinks. Run the static generator (you need Python≥2.6 and the Python Imaging Library):

$ cd ../scanner
$ ./main.py ../web/albums ../web/cache

After it finishes, you will be all set. Simply have your web server serve pages out of your web directory. You may want to do the scanning step in a cronjob, if you don't use the deployment makefiles mentioned below.

Nginx configuration for static-only

Please keep in mind this will not provide any kind of access-restrictions.

server {
    listen 80;
    server_name photos.jasondonenfeld.com;
    location / {
        index index.html;
        root /var/www/htdocs/photos.jasondonenfeld.com;

Now after deploying /var/www/htdocs/photos.jasondonenfeld.com should contain:

  • index.html
  • js/
  • css/
  • img/
  • albums/
  • cache/

You can easily manage that by creating a folder structure and copying relevant files over:

$ cd ..
photofloat ~ $ mkdir <deployment-folder>/js <deployment-folder>/css
$ cp web/js/scripts.min.js <deployment-folder>/js
$ cp web/css/styles.min.css <deployment-folder>/css
$ cp -a fonts img index.html <deployment-folder>/

For easy updates albums and cache can be set to also live in <deployment-folder>, this is especially recommended if you are using the optional flask app mentioned in the following section.

Do not keep any of your config or python-files where the webserver can read or write to, the deployment-config.mk is most sensitive. If you only want the static html/json & javascript application you are done now!

Optional: Server-side Authentication using flask

The JavaScript application uses a very simple API to determine if a photo can be viewed or not. If a JSON file returns error 403, the album is hidden from view. To authenticate, POST a username and a password to /auth. If unsuccessful, 403 is returned. If successful, 200 is returned, and the previously denied json files may now be requested. If an unauthorized album is directly requested in a URL when the page loads, an authentication box is shown.

subPhotoFloat ships with an optional server side component called FloatApp to facilitate this, which lives in scanner/floatapp. It is a simple Flask-based Python web application.


We need to install flask and other dependencies, ideally in a virtualenv, as this will keep your system-wide python installation clean and you can easily install more packages or different versions.

$ cd scanner
$ virtualenv venv
$ source venv/bin/activate
$ pip install -r requirements.txt

Edit the app.cfg configuration file

$ vim floatapp/app.cfg

Give this file a correct username and password for an admin, as well as a secret token. The admin user is allowed to call /scan, which automatically runs the scanner script mentioned in the previous section.

In app.cfg you may also add elements to the PERMISSION_MAP as follows. The dictionary takes any path (to either an album or an image) and restricts any album or image matching that path to the listed tokens.

    'private': ['thisisatoken'],
    'alsoprivate/butonlythis.jpg': ['morethan', 'onetoken'],

Tokens can contain anything you wish and you can add as many paths and tokes as you require. One match in the PERMISSION_MAP will allow access even if another rule would forbid it. The admin is allowed to see any album or image.

Configure nginx

FloatApp makes use of X-Accel-Buffering and X-Accel-Redirect to force the server-side component to have minimal overhead when serving images via flask. Here is an example nginx configuration that can be tweaked:

server {
    listen 80;
    server_name photos.jasondonenfeld.com;
    location / {
        index index.html;
        root /var/www/htdocs/photos.jasondonenfeld.com;

    include uwsgi_params;
    location /albums/ {
        uwsgi_pass unix:/var/run/uwsgi-apps/photofloat.socket;
    location /cache/ {
        uwsgi_pass unix:/var/run/uwsgi-apps/photofloat.socket;
    location /scan {
        uwsgi_pass unix:/var/run/uwsgi-apps/photofloat.socket;
    location /auth {
        uwsgi_pass unix:/var/run/uwsgi-apps/photofloat.socket;
    location /photos {
        uwsgi_pass unix:/var/run/uwsgi-apps/photofloat.socket;

    location /internal-cache/ {
        alias /var/www/uwsgi/photofloat/cache/;
    location /internal-albums/ {
        alias /var/www/uwsgi/photofloat/albums/;

Note that the internal-* paths must match that of app.cfg, since the flask app will redirect the "external" /albums and /cache paths to internal ones set in that config.

Configure uwsgi (or use an alternate wsgi-provider)

$ cat /etc/uwsgi.d/photofloat.ini
chdir = /var/www/uwsgi/%n
master = true
uid = %n
gid = %n
chmod-socket = 660
chown-socket = nginx:nginx
socket = /var/run/uwsgi-apps/%n.socket
logto = /var/log/uwsgi/%n.log
virtualenv = /var/www/uwsgi/photofloat/scanner/venv
processes = 4
idle = 1800
die-on-idle = true
plugins = python27
module = floatapp:app

Change the paths for chdir, socket, logto and virtualenv to your preference. Naturally, you can use any of the options available to deploy the flask app.

Optional: Deployment Makefiles

Both the scanner and the webpage have a make deploy target, and the scanner has a make scan target, to automatically deploy assets to a remote server and run the scanner. For use, customize deployment-config.mk in the root of the project, and carefully read the Makefiles to learn what's happening.

Be aware: you will very likely have to adapt the deploy-instructions to what you have deployed on your server.
If you are using the flask-app you will most likely not need the Makefiles.

Mailing List & Suggestions

If you have any suggestions, feel free to contact the subPhotoFloat community via our mailing list. We're open to adding all sorts of features and working on integration points with other pieces of software.

Note: As the project is 8+ years old, the mailing list has slowed down a bit, if you do not get an answer immediately, please be patient and give other users some time to respond.

This app is also fairly small, so this might be the perfect project to try and add some small features yourself. For reference you may want to look at the flask & nginx documentation.


Copyright (C) 2010 - 2014 Jason A. Donenfeld. All Rights Reserved.

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.