Compare commits
10 commits
f468ec5b6a
...
0e2f8a55ce
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e2f8a55ce | |||
| b859fece88 | |||
| 90638fd72a | |||
| ee699df9e6 | |||
| e124ce6501 | |||
| d0c09b628b | |||
| 72f3505e7b | |||
| 486990c5ed | |||
| e79c29fdcd | |||
| c289c2591a |
7 changed files with 133 additions and 36 deletions
|
|
@ -1,4 +1,4 @@
|
|||
placeany.site {
|
||||
encode zstd gzip
|
||||
reverse_proxy http://localhost:5099
|
||||
encode zstd gzip
|
||||
reverse_proxy http://localhost:8080
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,5 +12,5 @@ COPY wsgi.py wsgi.py
|
|||
|
||||
VOLUME images
|
||||
|
||||
ENTRYPOINT waitress-serve --host 127.0.0.1 --port 5099 wsgi:app
|
||||
ENTRYPOINT waitress-serve wsgi:app
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ FROM app AS imgs
|
|||
ARG images
|
||||
copy ${images} images
|
||||
|
||||
ENTRYPOINT waitress-serve --host 127.0.0.1 --port 5099 wsgi:app
|
||||
ENTRYPOINT waitress-serve wsgi:app
|
||||
|
||||
|
|
|
|||
52
README.md
52
README.md
|
|
@ -4,10 +4,20 @@ A quick and simple service for getting pictures of whatever-you-want
|
|||
for use as placeholders in your designs or code. Just put your image
|
||||
size (width & height) after the URL and you'll get a placeholder.
|
||||
|
||||
Similar URL API as [Placekitten]([http://placekitten.com](https://web.archive.org/web/20110504042732/http://placekitten.com/)).
|
||||
There is also a bookmarklet service which enhances sites with many images.
|
||||
|
||||
There is also a bookmarklet service which works the same as
|
||||
[Horse_ebookmarklet]([http://www.heyben.com/horse_ebookmarklet/](https://web.archive.org/web/20120223050454/http://www.heyben.com/horse_ebookmarklet/)).
|
||||
Inspirations:
|
||||
|
||||
- https://web.archive.org/web/20110504042732/http://placekitten.com/
|
||||
- https://web.archive.org/web/20120223050454/http://www.heyben.com/horse_ebookmarklet/.
|
||||
|
||||
## Example calls
|
||||
|
||||
Generates an image, 200px wide and 300px tall:
|
||||
http://localhost:8080/200/300
|
||||
|
||||
Generates an image in grayscale, 200px wide and 300px tall:
|
||||
http://localhost:8080/g/200/300
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -22,29 +32,43 @@ First, create an image collection.
|
|||
1. Go to the code: `cd path/to/holder`. Copy `images` folder to it.
|
||||
1. Create and activate a virtualenv.
|
||||
1. Get dependencies in place: `pip install -r requirements.txt`
|
||||
1. Start the app: `waitress-serve --host 127.0.0.1 --port 5099 wsgi:app`
|
||||
1. Go to [http://localhost:5099](http://localhost:5099) in your web browser.
|
||||
1. Start the app: `waitress-serve wsgi:app`
|
||||
1. Go to [http://localhost:8080](http://localhost:8080) in your web browser.
|
||||
1. Done!
|
||||
|
||||
### Run as Container
|
||||
|
||||
The most easy and portable way to use this is to use Docker or Podman.
|
||||
In this build, waitress is used for production readyness. Port 5099 is
|
||||
instead used.
|
||||
In this build, waitress is used for production readyness.
|
||||
|
||||
podman build .
|
||||
podman run -it -p 5099:5099 -v ./images:/app/images <container id>
|
||||
podman run -it -p 8080:8080 -v ./images:/app/images <container id>
|
||||
|
||||
If you wish to embed images in container as well, use alternate
|
||||
Containerfile.
|
||||
|
||||
podman build -f Containerfile.aio --build-arg images=./images .
|
||||
podman run -it -p 5099:5099 <container id>
|
||||
podman run -it -p 8080:8080 <container id>
|
||||
|
||||
## Example calls
|
||||
### Run behind reverse proxy
|
||||
|
||||
# generates an image, 200px wide and 300px tall
|
||||
http://localhost:5000/200/300
|
||||
A reverse proxy in front of placeany is recommended. An example
|
||||
Caddyfile is available to make https "just work", but Nginx+certbot
|
||||
will be equally fine.
|
||||
|
||||
# generates an image in grayscale, 200px wide and 300px tall
|
||||
http://localhost:5000/g/200/300
|
||||
## Podman tip: generate systemd files
|
||||
|
||||
Make sure to enable lingering user processes.
|
||||
|
||||
loginctl enable-linger $USER
|
||||
|
||||
Then, create and change directory to systemd.
|
||||
|
||||
mkdir -p .config/systemd/user
|
||||
cd .config/systemd/user
|
||||
|
||||
Now, generate the systemd user service.
|
||||
|
||||
podman generate systemd --new -f -n placeany
|
||||
|
||||
Your container can now be enabled and started.
|
||||
|
|
|
|||
|
|
@ -37,10 +37,9 @@
|
|||
<h1>placeany</h1>
|
||||
<p>A quick and simple service for getting pictures for use as placeholders in your designs or code.
|
||||
Just put your image size (width & height) after our URL and you'll get a placeholder.</p>
|
||||
<pre><output>Examples:
|
||||
<a href="/200/300">{{ url }}/200/300</a>
|
||||
<a href="/g/200/300">{{ url }}/g/200/300</a>
|
||||
</output></pre>
|
||||
<output>Like this: <a href="/200/300">{{ url }}/200/300</a>
|
||||
<br>or: <a href="/g/200/300">{{ url }}/g/200/300</a>
|
||||
</output>
|
||||
</article>
|
||||
<div class="unit">
|
||||
<img src="/450/230" alt="">
|
||||
|
|
|
|||
47
templates/list.html
Normal file
47
templates/list.html
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>placeany</title>
|
||||
<style>
|
||||
body {
|
||||
width: 900px;
|
||||
margin: 100px auto;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>placeany</h1>
|
||||
<p>The following images are used on this placeany instance.
|
||||
You may request a specific image by adding <b>?image=n</b> to your request
|
||||
(where <b>n</b> is the number of the image you want), or filter the options
|
||||
by adding multiple values like this: <b>?image=n1x&image=n2&image=n3</b>.</p>
|
||||
<div class="grid">
|
||||
{% for n in range(count) %}
|
||||
<div class="unit">
|
||||
<img src="/125/125?image={{ n }}" alt="">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</main>
|
||||
<hr>
|
||||
<footer role="contentinfo">
|
||||
Also available as a <a href="/bookmarklet">Bookmarklet Service</a>.<br>
|
||||
Source on <a href="https://github.com/madr/placeany">GitHub</a>.
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
53
wsgi.py
53
wsgi.py
|
|
@ -2,7 +2,7 @@ import os
|
|||
import random
|
||||
from io import BytesIO
|
||||
|
||||
from flask import Flask, render_template, request, send_file
|
||||
from flask import Flask, Response, render_template, request, send_file
|
||||
from flask_caching import Cache
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
|
|
@ -10,6 +10,7 @@ app = Flask(__name__)
|
|||
|
||||
GREY = "G"
|
||||
COLOR = "RGB"
|
||||
IMAGE_DIR = "./images"
|
||||
|
||||
cache = Cache(config={"CACHE_TYPE": "SimpleCache"})
|
||||
|
||||
|
|
@ -17,23 +18,45 @@ app = Flask(__name__)
|
|||
cache.init_app(app)
|
||||
|
||||
|
||||
def get_cropped_image(x, y, grey=False):
|
||||
@cache.memoize(1)
|
||||
def get_cropped_image(x, y, s, grey=False, retries=0):
|
||||
"""crops a random image from collection"""
|
||||
im_src = random.choice(os.listdir("./images"))
|
||||
im = Image.open(f"images/{im_src}")
|
||||
if retries > 10:
|
||||
return None
|
||||
options = os.listdir(IMAGE_DIR)
|
||||
try:
|
||||
selection = list(
|
||||
filter(
|
||||
lambda i: i in range(0, len(options)),
|
||||
map(int, s),
|
||||
)
|
||||
)
|
||||
except ValueError:
|
||||
return None
|
||||
match len(selection):
|
||||
case 0:
|
||||
im_src = random.choice(options)
|
||||
case 1:
|
||||
im_src = options[selection[0]]
|
||||
case _:
|
||||
im_src = options[random.choice(selection)]
|
||||
im = Image.open(f"{IMAGE_DIR}/{im_src}")
|
||||
out = BytesIO()
|
||||
max_x, max_y = im.size
|
||||
if x < max_x or y < max_y:
|
||||
im = ImageOps.fit(im, (x, y))
|
||||
if x > max_x and y > max_y:
|
||||
return get_cropped_image(x, y, grey, retries + 1)
|
||||
im = ImageOps.fit(im, (x, y))
|
||||
if grey:
|
||||
im = ImageOps.grayscale(im)
|
||||
im.save(out, "WEBP", quality=50)
|
||||
im.save(out, "WEBP", quality=80)
|
||||
out.seek(0)
|
||||
return out
|
||||
|
||||
|
||||
def make_response(x, y, color_mode=COLOR):
|
||||
im = get_cropped_image(x, y, color_mode == GREY)
|
||||
def make_response(x, y, s, color_mode=COLOR):
|
||||
im = get_cropped_image(x, y, s, color_mode == GREY)
|
||||
if not im:
|
||||
return Response(status=401)
|
||||
return send_file(im, mimetype="image/webp")
|
||||
|
||||
|
||||
|
|
@ -49,16 +72,20 @@ def bookmarklet():
|
|||
return render_template("bookmarklet.html", url=u)
|
||||
|
||||
|
||||
@app.route("/images")
|
||||
def collection():
|
||||
c = len(os.listdir(IMAGE_DIR))
|
||||
return render_template("list.html", count=c)
|
||||
|
||||
|
||||
@app.route("/<int:x>/<int:y>")
|
||||
@cache.cached(10)
|
||||
def generate(x, y):
|
||||
return make_response(x, y, COLOR)
|
||||
return make_response(x, y, request.args.getlist("image"), COLOR)
|
||||
|
||||
|
||||
@app.route("/g/<int:x>/<int:y>")
|
||||
@cache.cached(10)
|
||||
def generate_grey(x, y):
|
||||
return make_response(x, y, GREY)
|
||||
return make_response(x, y, request.args.getlist("image"), GREY)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue