Skip to content

Commit

Permalink
calculate job height on server instead of leaving it to the client
Browse files Browse the repository at this point in the history
  • Loading branch information
tykling committed Nov 4, 2024
1 parent 7808b07 commit 5544c85
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 56 deletions.
28 changes: 24 additions & 4 deletions src/images/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""The Image model."""

# mypy: disable-error-code="var-annotated"
import math
from fractions import Fraction

from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
Expand Down Expand Up @@ -45,9 +48,14 @@ class Image(BaseFile):
help_text="EXIF data for the image in JSON format.",
)

@property
def aspect_ratio(self) -> Fraction:
"""Return job AR as a Fraction."""
return Fraction(self.width, self.height)

def create_jobs(self) -> None:
"""Create jobs for missing versions for this image."""
# exif data
# get exif data?
if self.exif is None:
job, created = ImageExifExtractionJob.objects.get_or_create(
basefile=self,
Expand All @@ -61,11 +69,23 @@ def create_jobs(self) -> None:
continue
# file missing, a new job must be created
_, (_, filetype, ratio, _, width), _ = version.deconstruct()
if version.height:
height = version.height
else:
height = self.calculate_version_height(width=width, ratio=ratio if ratio else self.aspect_ratio)
job, created = ImageConversionJob.objects.get_or_create(
basefile=self,
path=version.name,
width=width,
height=height,
custom_aspect_ratio=bool(ratio),
filetype=filetype,
aspect_ratio_numerator=ratio.split("/")[0] if ratio else None,
aspect_ratio_denominator=ratio.split("/")[1] if ratio else None,
path=version.name,
)

def calculate_version_height(self, width: int, ratio: Fraction) -> int:
"""Calculate the height for an image version."""
if ratio != self.aspect_ratio:
# custom aspect ratio
return math.floor(width / ratio)
# maintain original AR
return math.floor(width / self.aspect_ratio)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 5.1.2 on 2024-11-04 16:04

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('jobs', '0003_alter_basejob_useragent_and_more'),
]

operations = [
migrations.RemoveField(
model_name='imageconversionjob',
name='aspect_ratio_denominator',
),
migrations.RemoveField(
model_name='imageconversionjob',
name='aspect_ratio_numerator',
),
migrations.AddField(
model_name='imageconversionjob',
name='height',
field=models.PositiveIntegerField(default=100, help_text='The desired height of the converted image.'),
preserve_default=False,
),
]
18 changes: 18 additions & 0 deletions src/jobs/migrations/0005_imageconversionjob_custom_aspect_ratio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2024-11-04 16:40

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('jobs', '0004_remove_imageconversionjob_aspect_ratio_denominator_and_more'),
]

operations = [
migrations.AddField(
model_name='imageconversionjob',
name='custom_aspect_ratio',
field=models.BooleanField(default=False, help_text='True if this job needs cropping to a custom AR, False if no crop is needed.'),
),
]
30 changes: 11 additions & 19 deletions src/jobs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,39 +90,31 @@ class ImageConversionJob(BaseJob):

width = models.PositiveIntegerField(help_text="The desired width of the converted image.")

height = models.PositiveIntegerField(help_text="The desired height of the converted image.")

filetype = models.CharField(
max_length=10,
validators=[validate_image_filetype],
help_text="The desired file type for this job.",
)

aspect_ratio_numerator = models.PositiveIntegerField(
null=True,
blank=True,
help_text="The numerator part of the desired aspect ratio of the image. Leave blank to keep original AR.",
)

aspect_ratio_denominator = models.PositiveIntegerField(
null=True,
blank=True,
help_text="The denominator part of the desired aspect ratio of the image. Leave blank to keep original AR.",
custom_aspect_ratio = models.BooleanField(
default=False,
help_text="True if this job needs cropping to a custom AR, False if no crop is needed.",
)

@property
def aspect_ratio(self) -> Fraction | None:
"""Return image AR as a Fraction."""
try:
return Fraction(self.aspect_ratio_numerator, self.aspect_ratio_denominator)
except TypeError:
return None
def aspect_ratio(self) -> Fraction:
"""Return job AR as a Fraction."""
return Fraction(self.width, self.height)

def get_result_path(self) -> tuple[Path, str]:
"""Return the path and filename for the job result."""
orig = Path(self.basefile.original.path)
path = orig.parent / orig.stem
if self.aspect_ratio:
path /= f"{self.aspect_ratio_numerator}_{self.aspect_ratio_denominator}"
# make sure the result path exists
if self.custom_aspect_ratio:
# add /4_3/ to the path for AR 4/3
path /= str(self.aspect_ratio).replace("/", "_")
path.mkdir(parents=True, exist_ok=True)
filename = f"{self.width}w.{self.filetype.lower()}"
return path, filename
Expand Down
48 changes: 15 additions & 33 deletions src/jobs/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,16 @@ class JobRequestSchema(Schema):
client_uuid: uuid.UUID


class ImageConversionJobResponseSchema(Schema):
"""Schema used for representing an image conversion job in a response."""
class JobResponseSchema(Schema):
"""Base schema for representing an image conversion job in a response."""

job_uuid: uuid.UUID
filetype: str
width: int
aspect_ratio_numerator: int | None = None
aspect_ratio_denominator: int | None = None
job_type: str
basefile_uuid: uuid.UUID
user_uuid: uuid.UUID | None = None
client_uuid: uuid.UUID | None = None
useragent: str | None = None
finished: bool
job_type: str
job_uuid: uuid.UUID
user_uuid: uuid.UUID | None = None
useragent: str | None = None

@staticmethod
def resolve_job_uuid(obj: BaseJob, context: dict[str, HttpRequest]) -> uuid.UUID:
Expand All @@ -50,33 +46,19 @@ def resolve_user_uuid(obj: BaseJob, context: dict[str, HttpRequest]) -> uuid.UUI
return obj.user_id # type: ignore[no-any-return]


class ExifExtractionJobResponseSchema(Schema):
"""Schema used for representing an exif metadata extraction job in a response."""
class ImageConversionJobResponseSchema(JobResponseSchema):
"""Schema used for representing an image conversion job in a response."""

job_uuid: uuid.UUID
job_type: str
basefile_uuid: uuid.UUID
user_uuid: uuid.UUID | None = None
client_uuid: uuid.UUID | None = None
useragent: str | None = None
finished: bool
filetype: str
width: int
height: int
custom_aspect_ratio: bool

@staticmethod
def resolve_job_uuid(obj: BaseJob, context: dict[str, HttpRequest]) -> uuid.UUID:
"""Get the value for the job_uuid field."""
return obj.uuid # type: ignore[no-any-return]

@staticmethod
def resolve_basefile_uuid(obj: BaseJob, context: dict[str, HttpRequest]) -> uuid.UUID:
"""Get the value for the basefile_uuid field."""
if isinstance(obj, dict) and "basefile_uuid" in obj:
return obj["basefile_uuid"] # type: ignore[no-any-return]
return obj.basefile_id # type: ignore[no-any-return]
class ExifExtractionJobResponseSchema(JobResponseSchema):
"""Schema used for representing an exif metadata extraction job in a response."""

@staticmethod
def resolve_user_uuid(obj: BaseJob, context: dict[str, HttpRequest]) -> uuid.UUID:
"""Get the value for the user_uuid field."""
return obj.user_id # type: ignore[no-any-return]
# this job schema has no extra fields


class SingleJobResponseSchema(ApiResponseSchema):
Expand Down

0 comments on commit 5544c85

Please sign in to comment.