Skip to content

Commit

Permalink
Merge pull request #292 from uploadcare/feature/267-update_transforma…
Browse files Browse the repository at this point in the history
…tions

Update transformations/image.py
  • Loading branch information
evgkirov committed Apr 9, 2024
2 parents 5491724 + 2488591 commit d85a26c
Show file tree
Hide file tree
Showing 13 changed files with 549 additions and 41 deletions.
29 changes: 25 additions & 4 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,37 @@ The format is based on [Keep a
Changelog](https://keepachangelog.com/en/1.0.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [5.0.2](https://github.com/uploadcare/pyuploadcare/compare/v5.0.1...v5.0.2) - unreleased
## [5.1.0](https://github.com/uploadcare/pyuploadcare/compare/v5.0.1...v5.1.0) - 2024-04-09

### Added

- For `File`:
- `detect_faces()` method to [detect faces in images](https://uploadcare.com/docs/intelligence/face-detection/).
- For `ImageTransformation`:
- `text()` method to allow adding [text overlays](https://uploadcare.com/docs/transformations/image/overlay/#overlay-text) to images.
- `rect()` method to allow adding [solid color overlays](https://uploadcare.com/docs/transformations/image/overlay/#overlay-solid) to images.
- `strip_meta()` method to [control the presence of EXIF metadata](https://uploadcare.com/docs/transformations/image/compression/#meta-information-control) in the resulting image.
- `border_radius()` method to [add rounded corners](https://uploadcare.com/docs/transformations/image/resize-crop/#operation-border-radius).
- `zoom_objects()` method to [zoom in on objects](https://uploadcare.com/docs/transformations/image/resize-crop/#operation-zoom-objects).
- `rasterize()` method to [rasterize SVG images](https://uploadcare.com/docs/transformations/image/svg/).
- `detect_faces()` method, which provides [face detection in images](https://uploadcare.com/docs/intelligence/face-detection/). While `ImageTransformation.detect_faces()` ensures consistency within the `ImageTransformation` API, you are more likely to use `File.detect_faces()` to obtain face detection results.

### Changed

- [Blocks](https://github.com/uploadcare/blocks) have been updated to [v0.36.0](https://github.com/uploadcare/blocks/releases)
- For `ImageTransformation`:
- The `overlay()` and `overlay_self()` methods now treat `overlay_width` and `overlay_height` parameters as optional.
- Unified `gif2video()`, `gif2video_format()`, and `gif2video_quality()` methods into a single `gif2video()` method. The `format` and `quality` parameters can now be accepted directly in the `gif2video()` method.

### Fixed

- Django forms: Any modifications made in an image editor are now correctly restored when editing the same image again. Previously, the editor state was not restored, and the original image was displayed instead. [via uploadcare/blocks#615](https://github.com/uploadcare/blocks/issues/615).

### Deprecated

- For `ImageTransformation`:
- Deprecated the separate `gif2video_format` and `gif2video_quality` methods. Please use the `format` and `quality` parameters directly in the `gif2video` method for setting these properties.

## [5.0.1](https://github.com/uploadcare/pyuploadcare/compare/v5.0.0...v5.0.1) - 2024-03-02

### Changed
Expand Down Expand Up @@ -50,14 +71,14 @@ Additionally, please take note that some settings have been renamed in this upda
- [Pydantic](https://docs.pydantic.dev) has been updated to Version 2. Projects dependent on Pydantic Version 1 may encounter errors due to incompatibility between Versions 1 and 2.
- Removed `tox.ini`. The recommended method for running tests locally is now through [act](https://github.com/nektos/act) with Docker.

- for Django settings (`UPLOADCARE = {...}`):
- For Django settings (`UPLOADCARE = {...}`):
- `widget_*` settings were renamed and moved:
- `UPLOADCARE["widget_version"]` to `UPLOADCARE["legacy_widget"]["version"]`
- `UPLOADCARE["widget_build"]` to `UPLOADCARE["legacy_widget"]["build"]`
- `UPLOADCARE["widget_variant"]` to `UPLOADCARE["legacy_widget"]["build"]` (this is not a typo: former `widget_build` and `widget_variant` settings were equialent)
- `UPLOADCARE["widget_url"]` to `UPLOADCARE["legacy_widget"]["override_js_url"]` and works regardless of `use_hosted_assets` value.

- for `pyuploadcare.dj.conf`:
- For `pyuploadcare.dj.conf`:
- Individual variables were moved into one dict called `config`. If you've accessed these settings from `pyuploadcare.dj.conf` module in your code, please migrate:
- `pub_key` to `config["pub_key"]`
- `secret` to `config["secret"]`
Expand All @@ -72,7 +93,7 @@ Additionally, please take note that some settings have been renamed in this upda
- `hosted_url`
- `local_url`

- for `pyuploadcare.dj.forms`:
- For `pyuploadcare.dj.forms`:
- `FileWidget` renamed to `LegacyFileWidget`. `FileWidget` is an all-new implementation now.
- By default, `FileWidget` is used. To use `LegacyFileWidget`, please set `UPLOADCARE["use_legacy_widget"]` to `True`

Expand Down
5 changes: 4 additions & 1 deletion docs/core_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ To check status of performed task use `status` method::

addon_task_status = uploadcare.addons_api.status(request_id, addon)

If addon execution produces new data for file (like an AWS recognition does), this data will be placed at `appdata` complex attribute of `File.info` (see `addons documentation`_)
If addon execution produces new data for file (like an AWS recognition does), this data will be placed at `appdata` complex attribute of `File.info` (see `addons documentation`_)::

file.update_info(include_appdata=True)
print(file.info["appdata"]["aws_rekognition_detect_labels"])
Expand Down Expand Up @@ -436,6 +436,8 @@ or by image transformation builder::
>>> file_.cdn_url
https://ucarecdn.com/a771f854-c2cb-408a-8c36-71af77811f3b/-/grayscale/-/flip/

To check out the list of available transformations, please refer to the `URL`_ API reference and to `ImageTransformation`_ class source code.


Useful links
------------
Expand All @@ -455,3 +457,4 @@ Useful links
.. _addons documentation: https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Add-Ons
.. _metadata documentation: https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/File-metadata
.. _file uploader: https://uploadcare.com/products/file-uploader/?utm_source=github&utm_campaign=pyuploadcare
.. _ImageTransformation: https://github.com/uploadcare/pyuploadcare/blob/main/pyuploadcare/transformations/image.py
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyuploadcare"
version = "5.0.1"
version = "5.1.0"
description = "Python library for Uploadcare.com"
authors = ["Uploadcare Inc <hello@uploadcare.com>"]
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion pyuploadcare/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# isort: skip_file
__version__ = "5.0.1"
__version__ = "5.1.0"

from pyuploadcare.resources.file import File # noqa: F401
from pyuploadcare.resources.file_group import FileGroup # noqa: F401
Expand Down
16 changes: 16 additions & 0 deletions pyuploadcare/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,3 +574,19 @@ def status(
json_response = self._client.get(url).json()
response = self._parse_response(json_response, response_class)
return cast(responses.AddonResponse, response)


class URLAPI(API):
resource_type = ""
response_classes = {
"detect_faces": entities.ImageInfoWithFaces,
}

def detect_faces(
self, file_uuid: Union[UUID, str]
) -> entities.ImageInfoWithFaces:
url = self._build_url(file_uuid, suffix="detect_faces/")
response_class = self._get_response_class("detect_faces")
json_response = self._client.get(url).json()
response = self._parse_response(json_response, response_class)
return cast(entities.ImageInfoWithFaces, response)
17 changes: 14 additions & 3 deletions pyuploadcare/api/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from uuid import UUID

from pydantic import BaseModel, EmailStr, Field, PrivateAttr
from typing_extensions import Annotated, Literal
from typing_extensions import Annotated, Literal, NamedTuple

from .metadata import META_KEY_MAX_LEN, META_KEY_PATTERN, META_VALUE_MAX_LEN

Expand Down Expand Up @@ -52,6 +52,13 @@ class GEOPoint(Entity):
longitude: float


class Face(NamedTuple):
x: int
y: int
width: int
height: int


WebhookEvent = Literal[
"file.uploaded",
"file.infected", # it will be deprecated in favor of info_upldated in the future updates
Expand All @@ -62,17 +69,21 @@ class GEOPoint(Entity):


class ImageInfo(Entity):
color_mode: ColorMode
color_mode: Optional[ColorMode] = None
orientation: Optional[int] = None
format: str
sequence: bool
sequence: Optional[bool] = None
height: int
width: int
geo_location: Optional[GEOPoint] = None
datetime_original: Optional[datetime] = None
dpi: Optional[Tuple[int, int]] = None


class ImageInfoWithFaces(ImageInfo):
faces: List[Face]


class AudioStreamInfo(Entity):
bitrate: Optional[Decimal] = None
codec: Optional[str] = None
Expand Down
15 changes: 15 additions & 0 deletions pyuploadcare/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
VideoConvertAPI,
WebhooksAPI,
)
from pyuploadcare.api.api import URLAPI
from pyuploadcare.api.auth import UploadcareAuth
from pyuploadcare.api.client import Client
from pyuploadcare.api.entities import ProjectInfo, Webhook, WebhookEvent
Expand Down Expand Up @@ -150,6 +151,19 @@ def __init__(
public_key=public_key,
)

self.cdn_client = Client(
base_url=cdn_base,
verify=(
DEFAULT_SSL_CONTEXT
if verify_upload_ssl is True
else verify_upload_ssl
),
timeout=timeout,
user_agent_extension=user_agent_extension,
retry_throttled=retry_throttled,
public_key=public_key,
)

api_config = {
"public_key": public_key,
"secret_key": secret_key,
Expand All @@ -168,6 +182,7 @@ def __init__(
self.project_api = ProjectAPI(client=self.rest_client, **api_config) # type: ignore
self.metadata_api = MetadataAPI(client=self.rest_client, **api_config) # type: ignore
self.addons_api = AddonsAPI(client=self.rest_client, **api_config) # type: ignore
self.url_api = URLAPI(client=self.cdn_client, **api_config)

def file(
self,
Expand Down
7 changes: 6 additions & 1 deletion pyuploadcare/resources/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
import logging
import re
import time
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
from uuid import UUID

from pyuploadcare.api.entities import (
DocumentConvertFormatInfo,
DocumentConvertInfo,
Face,
VideoConvertInfo,
)
from pyuploadcare.exceptions import (
Expand Down Expand Up @@ -499,6 +500,10 @@ def get_converted_document_group(
group_id = response.format.converted_groups[format]
return self._client.file_group(group_id)

def detect_faces(self) -> List[Face]:
response = self._client.url_api.detect_faces(self.uuid)
return response.faces


class FileFromUrl:
"""Contains the logic around an upload from url.
Expand Down
9 changes: 9 additions & 0 deletions pyuploadcare/transformations/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from enum import Enum
from typing import List, Optional, Union
from urllib.parse import quote

from typing_extensions import Self

Expand Down Expand Up @@ -28,6 +29,14 @@ def set(self, transformation_name: str, parameters: List[str]) -> Self:
self._effects.append(effect)
return self

def _escape_percent(self, value: Union[str, int]) -> str:
return str(value).replace("%", "p")

def _escape_text(self, value: str) -> str:
return quote(
value.replace("~", "~~").replace("/", "~s").replace("\n", "~n")
)

def _prefix(self, file_id: str) -> str:
return f"{file_id}/"

Expand Down
Loading

0 comments on commit d85a26c

Please sign in to comment.