Updated readme, changes to sample to match, some smaller fixes
This commit is contained in:
parent
f79eda5ec6
commit
d10da55654
205
README.md
205
README.md
@ -1,48 +1,61 @@
|
||||
# PhotoFloat
|
||||
### A Web 2.0 Photo Gallery Done Right via Static JSON & Dynamic Javascript
|
||||
#### by Jason A. Donenfeld (<Jason@zx2c4.com>)
|
||||
# subPhotoFloat – A [Photofloat](https://git.zx2c4.com/PhotoFloat/) fork
|
||||
## Web Photo Gallery via Static JSON & Dynamic Javascript with a Python Backend
|
||||
|
||||
![Screenshot](http://data.zx2c4.com/photo-float-small.jpg)
|
||||
by Jason A. Donenfeld (<Jason@zx2c4.com>)
|
||||
with some changes by Markus Pawlata (<markus@derdritte.net>) and other [collaborators](https://git.jocke.no/photofloat/log/?h=patches).
|
||||
|
||||
PhotoFloat is an open source web photo gallery aimed at sleekness and speed. It keeps with an old hat mentality, preferring to work over directory structures rather than esoteric photo database management software. Everything it generates is static, which means it's extremely fast.
|
||||
![Screenshot](https://photos.derdritte.net/img/screenshot.png)
|
||||
|
||||
[Check out a demo!](http://photos.jasondonenfeld.com/#santa_fe_and_telluride_8.19.10-8.27.10/western_202.jpg)
|
||||
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!](https://photos.derdritte.net/#!/random)
|
||||
|
||||
## How It Works
|
||||
|
||||
PhotoFloat consists of two segments – a Python script and a JavaScript application.
|
||||
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. PhotoFloat 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.
|
||||
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:
|
||||
|
||||
* Animations to make the interface feel nice
|
||||
* Separate album view and photo view
|
||||
* Album metadata pre-fetching
|
||||
* Photo pre-loading
|
||||
* Recursive async randomized tree walking album thumbnail algorithm
|
||||
* Smooth up and down scaling
|
||||
* Mouse-wheel support
|
||||
* Swipe support (on mobile)
|
||||
* Metadata display
|
||||
* Consistant hash url format
|
||||
* Consistent hash url format
|
||||
* Linkable states via ajax urls
|
||||
* Static rendering for googlebot conforming to the AJAX crawling spec.
|
||||
* Facebook meta tags for thumbnail and post type
|
||||
* 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, the slickest and fastest, most minimal but still well-featured photo gallery app on the net.
|
||||
It is, essentially, a very slick and fast, fairly minimal but still well-featured photo gallery app.
|
||||
|
||||
## Dependencies
|
||||
|
||||
* python >= 2.6
|
||||
* pillow >= 5.3.0
|
||||
* nginx (or any webserver, really)
|
||||
|
||||
### Optional
|
||||
|
||||
* flask >= 0.11 (for authentication)
|
||||
* flask-login >= 0.4.1 (for authentication)
|
||||
* virtualenv (this is nice, [believe me](https://docs.python-guide.org/dev/virtualenvs/#lower-level-virtualenv))
|
||||
* ffmpeg (for video conversion)
|
||||
|
||||
## Installation
|
||||
|
||||
#### Download the source code from the git repository:
|
||||
### Download the source code from the git repository
|
||||
|
||||
$ git clone git://git.zx2c4.com/PhotoFloat
|
||||
$ cd PhotoFloat
|
||||
$ git clone https://derdritte.net/gitea/markus/photofloat
|
||||
$ cd photofloat
|
||||
|
||||
#### Change or delete the Google Analytics ID tracker:
|
||||
### Change or delete the Google Analytics ID tracker
|
||||
|
||||
To delete:
|
||||
|
||||
@ -54,18 +67,18 @@ To change:
|
||||
|
||||
Modify the part that says UA-XXXXXX-X and put your own in there.
|
||||
|
||||
#### Tweak the index.html page to have a custom title or copyright notice.
|
||||
### Tweak the index.html page to have a custom title or copyright notice
|
||||
|
||||
$ vim web/index.html
|
||||
|
||||
#### Build the web page.
|
||||
### 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:
|
||||
### Generate the albums
|
||||
|
||||
Now that we're in the web directory, let's make a folder for cache and a folder for the pictures:
|
||||
|
||||
@ -79,89 +92,149 @@ When you're done, fill albums with photos and directories of photos. You can als
|
||||
|
||||
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.
|
||||
|
||||
## Optional: Server-side Authentication
|
||||
### 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.
|
||||
|
||||
PhotoFloat ships with an optional server side component called FloatApp to faciliate this, which lives in `scanner/floatapp`. It is a simple Flask-based Python web application.
|
||||
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.
|
||||
|
||||
#### Edit the app.cfg configuration file:
|
||||
### Installation
|
||||
|
||||
$ cd scanner/floatapp
|
||||
$ vim app.cfg
|
||||
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.
|
||||
|
||||
Give this file a correct username and password, for both an admin user and a photo user, 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.
|
||||
$ cd scanner
|
||||
$ virtualenv venv
|
||||
$ source venv/bin/activate
|
||||
$ pip install -r requirements.txt
|
||||
|
||||
#### Decide which albums or photos are protected:
|
||||
### Edit the app.cfg configuration file
|
||||
|
||||
$ vim auth.txt
|
||||
$ vim floatapp/app.cfg
|
||||
|
||||
This file takes one path per line. It restricts access to all photos in this path. If the path is a single photo, then that single photo is restricted.
|
||||
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.
|
||||
|
||||
#### Configure nginx:
|
||||
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.
|
||||
|
||||
FloatApp makes use of `X-Accel-Buffering` and `X-Accel-Redirect` to force the server-side component to have minimal overhead. Here is an example nginx configuration that can be tweaked:
|
||||
…
|
||||
PERMISSION_MAP = {
|
||||
'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](https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/) 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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/ {
|
||||
internal;
|
||||
alias /var/www/uwsgi/photofloat/cache/;
|
||||
}
|
||||
location /internal-albums/ {
|
||||
internal;
|
||||
alias /var/www/uwsgi/photofloat/albums/;
|
||||
}
|
||||
location /internal-cache/ {
|
||||
internal;
|
||||
alias /var/www/uwsgi/photofloat/cache/;
|
||||
}
|
||||
location /internal-albums/ {
|
||||
internal;
|
||||
alias /var/www/uwsgi/photofloat/albums/;
|
||||
}
|
||||
}
|
||||
|
||||
Note that the `internal-*` paths must match that of `app.cfg`. This makes use of uwsgi for execution:
|
||||
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.
|
||||
|
||||
metheny ~ # cat /etc/uwsgi.d/photofloat.ini
|
||||
### Configure uwsgi (or use an alternate wsgi-provider)
|
||||
|
||||
$ cat /etc/uwsgi.d/photofloat.ini
|
||||
[uwsgi]
|
||||
chdir = /var/www/uwsgi/%n
|
||||
master = true
|
||||
uid = %n
|
||||
gid = %n
|
||||
chmod-socket = 660
|
||||
chown-socket = %n:nginx
|
||||
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](http://flask.pocoo.org/docs/1.0/deploying/#self-hosted-options) 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 `Makefile`s 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 PhotoFloat community via [our mailing list](http://lists.zx2c4.com/mailman/listinfo/photofloat). We're open to adding all sorts of features and working on integration points with other pieces of software.
|
||||
If you have any suggestions, feel free to contact the subPhotoFloat community via [our mailing list](http://lists.zx2c4.com/mailman/listinfo/photofloat). 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.
|
||||
|
||||
## License
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
ADMIN_USERNAME = "misterscanner"
|
||||
ADMIN_PASSWORD = "ilovescanning"
|
||||
|
||||
PHOTO_USERNAME = "photos" # The GUI currently hardcodes 'photos', so don't change this
|
||||
PHOTO_PASSWORD = "myphotopassword"
|
||||
PERMISSION_MAP = {
|
||||
'album/or/image/path': ['token']
|
||||
}
|
||||
|
||||
ALBUM_PATH = "/var/www/uwsgi/photofloat/albums"
|
||||
ALBUM_ACCEL = "/internal-albums"
|
||||
|
@ -1 +0,0 @@
|
||||
path/to/some/place
|
@ -39,8 +39,9 @@ def login():
|
||||
logout_user()
|
||||
|
||||
if current_user.is_authenticated:
|
||||
return 'Already logged in.'
|
||||
elif (query_is_admin_user(request.form) or
|
||||
logout_user()
|
||||
|
||||
if (query_is_admin_user(request.form) or
|
||||
query_is_admin_user(request.args)):
|
||||
login_user(admin_user, remember=True)
|
||||
else:
|
||||
@ -48,7 +49,6 @@ def login():
|
||||
request.args.get('username', None))
|
||||
if user_id:
|
||||
login_user(load_user(user_id), remember=True)
|
||||
print "logged in {}".format(user_id)
|
||||
return 'You are now logged in.'
|
||||
|
||||
return ""
|
||||
@ -137,6 +137,9 @@ def cache_base(path):
|
||||
|
||||
|
||||
def has_permission(path):
|
||||
if not current_user.is_anonymous and current_user.is_admin:
|
||||
return True
|
||||
|
||||
for auth_path in permission_map.keys():
|
||||
# this is a protected object
|
||||
if (path.startswith(auth_path) or
|
||||
|
@ -15,6 +15,10 @@ class User(UserMixin):
|
||||
def __str__(self):
|
||||
return str(self.id)
|
||||
|
||||
@property
|
||||
def is_admin(self):
|
||||
return self.admin
|
||||
|
||||
admin_user = User("admin", True)
|
||||
|
||||
|
||||
|
BIN
web/img/screenshot.png
Normal file
BIN
web/img/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 250 KiB |
@ -17,7 +17,7 @@
|
||||
</a>
|
||||
<div id="photo-bar">
|
||||
<div id="photo-links">
|
||||
<a id="metadata-link" href="javascript:void(0)">show metadata</a> | <a id="original-link" target="_blank">download original</a><span id="fullscreen-divider"> | </span><a id="fullscreen" href="javascript:void(0)">fullscreen</a>
|
||||
<a id="metadata-link" href="javascript:void(0)">show metadata</a> | <a id="original-link">download original</a><span id="fullscreen-divider"> | </span><a id="fullscreen" href="javascript:void(0)">fullscreen</a>
|
||||
</div>
|
||||
<div id="metadata"></div>
|
||||
</div>
|
||||
@ -35,7 +35,7 @@
|
||||
<div id="loading">Loading...</div>
|
||||
</div>
|
||||
<div id="subalbums"></div>
|
||||
<div id="powered-by">Powered by <a href="https://derdritte.net/gitea/markus/photofloat">PhotoFloat</a></div>
|
||||
<div id="powered-by">Powered by <a href="https://derdritte.net/gitea/markus/photofloat">subPhotoFloat</a></div>
|
||||
</div>
|
||||
|
||||
<div id="error-overlay"></div>
|
||||
|
Loading…
Reference in New Issue
Block a user