Pridani editace fotek

This commit is contained in:
Michal Kváček
2023-10-27 22:30:59 +02:00
parent 322e61bce3
commit 3223d2ac00
17 changed files with 602 additions and 54 deletions
-2
View File
@@ -2,10 +2,8 @@ from typing import List, Optional
import strawberry
from fastapi import HTTPException
from starlette.status import HTTP_401_UNAUTHORIZED
from database import models
from decorators.endpoints import authenticated_user_only
from database.transaction import get_session
from graphql_schema.entities.resolvers.base import BaseMutationResolver
from graphql_schema.entities.resolvers.event import EventQueryResolver
from graphql_schema.entities.types.mutation_input import CreateEventInput, EditEventInput
+105 -19
View File
@@ -1,19 +1,25 @@
import asyncio
from typing import List
import os
import shutil
from typing import List, Optional
import strawberry
from PIL import Image
from sqlalchemy import delete, select
from background_jobs.elevation import add_terrain_elevation_to_photo
from background_jobs.photo import generate_thumbnail, resize_photo
from database import models
from decorators.endpoints import authenticated_user_only
from database.transaction import get_session
from graphql_schema.entities.resolvers.base import BaseQueryResolver
from graphql_schema.entities.resolvers.base import BaseQueryResolver, BaseMutationResolver
from graphql_schema.entities.resolvers.photo import PhotoMutationResolver
from graphql_schema.entities.types.types import Photo
from graphql_schema.entities.types.types import Photo, PhotoAdjustment
from logger import log
from paths import get_photo_basepath
from utils.file import delete_file
from utils.image import parse_exif_info, rotate_image
from utils.image import parse_exif_info, rotate_image_no_crop, adjust_image, resize_image, PhotoEditor
from utils.upload import handle_file_upload
from graphql_schema.entities.types.mutation_input import EditPhotoInput, UploadPhotoInput
from graphql_schema.entities.types.mutation_input import EditPhotoInput, UploadPhotoInput, AdjustmentInput
@strawberry.type
@@ -22,6 +28,10 @@ class PhotoQueries:
async def photos(root, info) -> List[Photo]:
return await BaseQueryResolver(Photo, models.Photo).get_list(user_id=info.context.user_id)
@strawberry.field()
async def photo(root, info, id: int) -> Photo:
return await BaseQueryResolver(Photo, models.Photo).get_one(id, user_id=info.context.user_id)
@strawberry.type
class PhotoMutation:
@@ -32,12 +42,16 @@ class PhotoMutation:
filename = await handle_file_upload(input.photo, path)
exif_info = await parse_exif_info(path, filename)
img = Image.open(f"{path}/{filename}")
photo = await PhotoMutationResolver().create(
user_id=info.context.user_id,
data={
"flight_id": input.flight_id,
"name": input.name,
"filename": filename,
"width": img.width,
"height": img.height,
"description": input.description,
"exposed_at": exif_info.get("datetime_original"),
"gps_latitude": exif_info.get("gps_latitude"),
@@ -47,7 +61,7 @@ class PhotoMutation:
},
)
info.context.background_tasks.add_task(resize_photo, path=path, filename=filename)
info.context.background_tasks.add_task(resize_photo, path=path, filename=filename, photo_id=photo.id)
info.context.background_tasks.add_task(generate_thumbnail, path=path, filename=filename)
if exif_info.get("gps_latitude") and exif_info.get("gps_longitude"):
@@ -62,28 +76,100 @@ class PhotoMutation:
@strawberry.mutation()
@authenticated_user_only()
async def rotate_photo(self, info, id: int, angle: int) -> Photo:
async def change_orientation(self, info, id: int, direction: str) -> Photo:
async with get_session() as db:
photo = (await db.scalars(
BaseQueryResolver(Photo, models.Photo).get_query(user_id=info.context.user_id, object_id=id)
)).one()
photo_filename = photo.filename
flight_id = photo.flight_id
photo_id = photo.id
photo_filename = photo.filename
photo_path = get_photo_basepath(flight_id)
original_filename = "_original_" + photo_filename
if not os.path.isfile(photo_path + "/" + original_filename):
shutil.copyfile(photo_path + "/" + photo_filename, photo_path + "/" + original_filename)
degrees_map = {
"clockwise": -90,
"counterClockwise": 90
}
# rotate original
editor = PhotoEditor(photo_path, original_filename)
editor.rotate(degrees=degrees_map[direction], crop_after_rotate=False)
editor.write_to_file(quality=100)
# rotate possibly adjusted image
editor = PhotoEditor(photo_path, photo_filename)
editor.rotate(degrees=degrees_map[direction], crop_after_rotate=False)
editor.write_to_file(quality=100)
info.context.background_tasks.add_task(generate_thumbnail, path=photo_path, filename=photo_filename)
async with get_session() as db:
photo = await models.Photo.update(db, {
"width": editor.img.width,
"height": editor.img.height
}, id=photo_id)
return Photo(**photo.as_dict())
@strawberry.mutation()
@authenticated_user_only()
async def adjust_photo(self, info, id: int, adjustment: AdjustmentInput) -> Photo:
async with get_session() as db:
photo = (await db.scalars(
BaseQueryResolver(Photo, models.Photo).get_query(user_id=info.context.user_id, object_id=id)
)).one()
flight_id = photo.flight_id
photo_filename = photo.filename
photo_as_dict = photo.as_dict()
await asyncio.gather(
rotate_image(
path=get_photo_basepath(flight_id),
filename=photo_filename,
angle=angle,
),
rotate_image(
path=get_photo_basepath(flight_id) + "/thumbs",
filename=photo_filename,
angle=angle,
),
photo_path = get_photo_basepath(flight_id)
# TOOD: presunout do samostatne metody
original_filename = "_original_" + photo_filename
if not os.path.isfile(photo_path + "/" + original_filename):
shutil.copyfile(photo_path + "/" + photo_filename, photo_path + "/" + original_filename)
editor = (
PhotoEditor(photo_path, original_filename)
.adjust(
brightness=adjustment.brightness,
contrast=adjustment.contrast,
sharpness=adjustment.sharpness,
saturation=adjustment.saturation
)
)
if adjustment.rotate:
rotate_angle = adjustment.rotate
editor.rotate(rotate_angle, adjustment.crop_after_rotate)
if adjustment.crop:
editor.crop(**adjustment.crop.to_dict())
editor.write_to_file(dest_filename=photo_filename)
info.context.background_tasks.add_task(generate_thumbnail, path=photo_path, filename=photo_filename)
async with get_session() as db:
await db.execute(delete(models.PhotoAdjustment).filter(models.PhotoAdjustment.photo_id == id))
crop_info = {"crop_" + key: value for key, value in adjustment.crop.to_dict().items()} if adjustment.crop else {}
await models.PhotoAdjustment.create(db, {
"photo_id": id,
"contrast": adjustment.contrast,
"saturation": adjustment.saturation,
"brightness": adjustment.brightness,
"rotate": adjustment.rotate,
"sharpness": adjustment.sharpness,
**crop_info
})
width, height = editor.img_size
await models.Photo.update(db, {"width": width, "height": height}, id=id)
return Photo(**photo_as_dict)
@strawberry.mutation()
@@ -77,6 +77,25 @@ class EditPhotoInput:
}
@strawberry.input
class CropInput(BaseGraphqlInputType):
left: float
top: float
width: float
height: float
@strawberry.input
class AdjustmentInput:
rotate: Optional[float] = 0
crop_after_rotate: Optional[bool] = True,
brightness: Optional[float] = 1
contrast: Optional[float] = 1
saturation: Optional[float] = 1
sharpness: Optional[float] = 1
crop: Optional[CropInput] = None
@strawberry_sqlalchemy_input(models.Flight, exclude_fields=[
"id", "aircraft_id", "deleted", "landing_airport_id", "takeoff_airport_id",
"takeoff_weather_info_id", "landing_weather_info_id", "gpx_track_filename", "event_id"
+8 -1
View File
@@ -14,7 +14,7 @@ from graphql_schema.dataloaders.multi_models import (
)
from graphql_schema.dataloaders.single_model import (
poi_dataloader, poi_type_dataloader, event_dataloader, aircraft_dataloader, airport_dataloader, cover_photo_loader,
airport_weather_info_loader, organizations_dataloader
airport_weather_info_loader, organizations_dataloader, flight_dataloader, photo_adjustment_dataloader, photo_dataloader
)
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type
from paths import (
@@ -65,6 +65,11 @@ class WeatherInfo:
pass
@strawberry_sqlalchemy_type(models.PhotoAdjustment)
class PhotoAdjustment:
photo: Photo = strawberry.field(resolver=lambda root: photo_dataloader.load(root.photo_id))
@strawberry_sqlalchemy_type(models.PointOfInterest)
class PointOfInterest:
type: Optional[PointOfInterestType] = strawberry.field(resolver=lambda root: poi_type_dataloader.load(root.type_id))
@@ -79,6 +84,8 @@ class Photo:
point_of_interest: Optional[PointOfInterest] = strawberry.field(
resolver=lambda root: poi_dataloader.load(root.point_of_interest_id)
)
flight: Flight = strawberry.field(resolver=lambda root: flight_dataloader.load(root.flight_id))
adjustment: Optional[PhotoAdjustment] = strawberry.field(resolver=lambda root: photo_adjustment_dataloader.load(root.id))
@strawberry_sqlalchemy_type(models.Flight)