Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

delegate thumbnails for raw images to external service #854

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions api/thumbnails.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import subprocess

import pyvips
from wand.image import Image
import requests

import api.util as util
import ownphotos.settings
Expand All @@ -16,14 +16,13 @@ def createThumbnail(inputPath, outputHeight, outputPath, hash, fileType):
completePath = os.path.join(
ownphotos.settings.MEDIA_ROOT, outputPath, hash + fileType
).strip()
with Image(filename=inputPath) as img:
with img.clone() as thumbnail:
thumbnail.format = "webp"
thumbnail.transform(resize="x" + str(outputHeight))
thumbnail.compression_quality = 95
thumbnail.auto_orient()
thumbnail.save(filename=completePath)
return completePath
json = {
"source": inputPath,
"destination": completePath,
"height": outputHeight,
}
response = requests.post("http://localhost:8003/", json=json).json()
return response["thumbnail"]
else:
bigThumbnailPath = os.path.join(
ownphotos.settings.MEDIA_ROOT, "thumbnails_big", hash + fileType
Expand Down
3 changes: 2 additions & 1 deletion requirements.dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ Faker==17.6.0
isort==5.12.0
setuptools==67.6.1
black==23.3.0
pyfakefs==5.2.0
pyfakefs==5.2.0
pytest==7.3.1
Empty file added service/__init__.py
Empty file.
Empty file added service/thumbnail/__init__.py
Empty file.
38 changes: 38 additions & 0 deletions service/thumbnail/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import gevent
from flask import Flask, request
from gevent.pywsgi import WSGIServer
from wand.image import Image

app = Flask(__name__)


def log(message):
print("thumbnail: {}".format(message))


@app.route("/", methods=["POST"])
def create_thumbnail():
try:
data = request.get_json()
source = data["source"]
destination = data["destination"]
height = data["height"]
except Exception:
return "", 400
log(f"creating for source={source} height={height}")
with Image(filename=source) as img:
with img.clone() as thumbnail:
thumbnail.format = "webp"
thumbnail.transform(resize=f"x{height}")
thumbnail.compression_quality = 95
thumbnail.auto_orient()
thumbnail.save(filename=destination)
log(f"created at location={destination}")
return {"thumbnail": destination}, 201


if __name__ == "__main__":
log("service starting")
server = WSGIServer(("0.0.0.0", 8003), app)
server_thread = gevent.spawn(server.serve_forever)
gevent.joinall([server_thread])
3 changes: 3 additions & 0 deletions service/thumbnail/test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
samples/*
!samples/.gitkeep
!samples/README.md
Empty file.
Empty file.
1 change: 1 addition & 0 deletions service/thumbnail/test/samples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
place any *image* files in this directory that you want to use as test data
47 changes: 47 additions & 0 deletions service/thumbnail/test/test_thumbnail_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import os

from pytest import fixture

from service.thumbnail.main import app


@fixture()
def client():
return app.test_client()


def test_must_fail_when_passing_empty_string(client):
response = client.post("/", data="")
assert response.status_code == 400


def test_must_fail_when_passing_invalid_json(client):
response = client.post("/", data="invalid json")
assert response.status_code == 400


def test_must_fail_when_passing_incomplete_json(client):
invalid_payloads = [
{"source": "foo"},
{"destination": "/tmp/result.webp"},
{"height": 100},
{"source": "foo", "destination": "/tmp/result.webp"},
{"destination": "/tmp/result.webp", "height": 100},
{"height": 100, "source": "foo"},
]
for payload in invalid_payloads:
response = client.post("/", json=payload)
assert response.status_code == 400


def test_should_create_thumbnail(client):
samples_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "samples")
samples = [f for f in os.listdir(samples_dir) if f not in [".gitkeep", "README.md"]]
thumbnail_path = "/tmp/result.webp"
for sample in samples:
if os.path.exists(thumbnail_path):
os.remove(thumbnail_path)
source = os.path.join(samples_dir, sample)
json = {"source": source, "destination": thumbnail_path, "height": 100}
response = client.post("/", json=json)
assert response.status_code == 201
Loading