Closed simonw closed 4 years ago
The apple_photos
table has an indexed uuid
column and a path
column which stores the full path to that photo file on disk.
I can write a custom Datasette plugin which takes the uuid
from the URL, looks up the path, then serves up a thumbnail of the jpeg or heic image file.
I'll prototype this is a one-off plugin first, then package it on PyPI for other people to install.
The plugin can be generalized: it can be configured to know how to take the URL path, look it up in ANY table (via a custom SQL query) to get a path on disk and then serve that.
Here's rendering code from my hacked-together not-yet-released S3 image proxy:
from starlette.responses import Response
from PIL import Image, ExifTags
import pyheif
for ORIENTATION_TAG in ExifTags.TAGS.keys():
if ExifTags.TAGS[ORIENTATION_TAG] == "Orientation":
break
...
# Load it into Pillow
if ext == "heic":
heic = pyheif.read_heif(image_response.content)
image = Image.frombytes(mode=heic.mode, size=heic.size, data=heic.data)
else:
image = Image.open(io.BytesIO(image_response.content))
# Does EXIF tell us to rotate it?
try:
exif = dict(image._getexif().items())
if exif[ORIENTATION_TAG] == 3:
image = image.rotate(180, expand=True)
elif exif[ORIENTATION_TAG] == 6:
image = image.rotate(270, expand=True)
elif exif[ORIENTATION_TAG] == 8:
image = image.rotate(90, expand=True)
except (AttributeError, KeyError, IndexError):
pass
# Resize based on ?w= and ?h=, if set
width, height = image.size
w = request.query_params.get("w")
h = request.query_params.get("h")
if w is not None or h is not None:
if h is None:
# Set h based on w
w = int(w)
h = int((float(height) / width) * w)
elif w is None:
h = int(h)
# Set w based on h
w = int((float(width) / height) * h)
w = int(w)
h = int(h)
image.thumbnail((w, h))
# ?bw= converts to black and white
if request.query_params.get("bw"):
image = image.convert("L")
# ?q= sets the quality - defaults to 75
quality = 75
q = request.query_params.get("q")
if q and q.isdigit() and 1 <= int(q) <= 100:
quality = int(q)
# Output as JPEG or PNG
output_image = io.BytesIO()
image_type = "JPEG"
kwargs = {"quality": quality}
if image.format == "PNG":
image_type = "PNG"
kwargs = {}
image.save(output_image, image_type, **kwargs)
return Response(
output_image.getvalue(),
media_type="image/jpeg",
headers={"cache-control": "s-maxage={}, public".format(365 * 24 * 60 * 60)},
)
datasette-media
will be able to handle this once I implement https://github.com/simonw/datasette-media/issues/3
As that seems to be closed, can you give a hint on how to make this work?
Sure, I should absolutely document this!
I'll add a proper section to the README, but for the moment here's how I do this.
First, install datasette
and the datasette-media
plugin.
Create a metadata.yaml
file with the following content:
plugins:
datasette-media:
photo:
sql: |-
select path as filepath, 200 as resize_height from apple_photos where uuid = :key
photo-big:
sql: |-
select path as filepath, 1024 as resize_height from apple_photos where uuid = :key
Now run datasette -m metadata.yaml photos.db
- thumbnails will be served at http://127.0.0.1:8001/-/media/photo/F4469918-13F3-43D8-9EC1-734C0E6B60AD and larger sizes of the image at http://127.0.0.1:8001/-/media/photo-big/A8B02C7D-365E-448B-9510-69F80C26304D
I also made myself two custom pages, one showing recent images and one showing random images.
To do this, install the datasette-template-sql
plugin and then create a templates/pages
directory and add these files:
recent-photos.html
<h1>Recent photos</h1>
<div>
{% for photo in sql("select * from apple_photos order by date desc limit 100") %}
<img src="/-/media/photo/{{ photo['uuid'] }}">
{% endfor %}
</div>
random-photos.html
<h1>Random photos</h1>
<div>
{% for photo in sql("with foo as (select * from apple_photos order by date desc limit 5000) select * from foo order by random() limit 100") %}
<img src="/-/media/photo/{{ photo['uuid'] }}">
{% endfor %}
</div>
Now run datasette -m metadata.yaml photos.db --template-dir=templates/
Visit http://127.0.0.1:8001/random-photos to see some random photos or http://127.0.0.1:8002/recent-photos for recent photos.
This is using this mechanism: https://datasette.readthedocs.io/en/stable/custom_templates.html#custom-pages
I'll add docs on using datasette-json-html
too.
A custom Datasette plugin that can be run locally on a Mac laptop which knows how to serve photos such that they can be seen in the browser.
Originally posted by @simonw in https://github.com/dogsheep/photos-to-sqlite/issues/19#issuecomment-624406285