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

Feat 4122 compress image python #160

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
81 changes: 81 additions & 0 deletions python/compress-image/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# 🖼️ Compress Image with TinyPNG and KrakenIO

A Python Cloud Function for compressing images without losing quality using [Tinypng API](https://tinypng.com/) and [KrakenIO](https://kraken.io/).


Example input with Tinypng:
```json
{
"provider":"tinypng",
"image":"iVBORw0KGgoAAAANSUhEUgAAAaQAAALiCAY...QoH9hbkTPQAAAABJRU5ErkJggg=="
}
```
Example input with KrakenIO:
```json
{
"provider":"krakenio",
"image":"iVBORw0KGgoAAAANSUhEUgAAAaQAAALiCAY...QoH9hbkTPQAAAABJRU5ErkJggg=="
}
```

Example output:
```json
{
"success":true,
"image":"iVBORw0KGgoAAAANSUhE...o6Ie+UAAAAASU5CYII="
}
```
Example error output:
```json
{
"success":false,
"image":"iVBORw0KGgoAAAANSUhE...o6Ie+UAAAAASU5CYII="
}
```

## 📝 Environment Variables

List of environment variables used by this cloud function:
- **TINYPNG_KEY** - Tinypng API Key
- **KRAKENIO_KEY** - KrakenIO API Key
- **KRAKENIO_SECRET_KEY** - KrakenIO Secret API Key


ℹ️ _Create your TinyPNG API key at https://tinypng.com/developers_. <br>
ℹ️ _Create your KrakenIO API key at https://kraken.io/docs/getting-started_. <br>


## 🚀 Deployment

1. Clone this repository, and enter this function folder:

```bash
git clone https://github.com/open-runtimes/examples.git
cd examples/python/compress-image
```

2. Enter this function folder and build the code:
```bash
docker run --rm --interactive --tty --volume $PWD:/usr/code openruntimes/python:v2-3.10 sh /usr/local/src/build.sh
```
As a result, a `code.tar.gz` file will be generated.

3. Start the Open Runtime:
```bash
docker run -p 3000:3000 -e INTERNAL_RUNTIME_KEY=secret-key -e INTERNAL_RUNTIME_ENTRYPOINT=main.py --rm --interactive --tty --volume $PWD/code.tar.gz:/tmp/code.tar.gz:ro openruntimes/python:v2-3.10 sh /usr/local/src/start.sh
```

> Make sure to replace `YOUR_API_KEY` with your key.
Your function is now listening on port `3000`, and you can execute it by sending `POST` request with appropriate authorization headers. To learn more about runtime, you can visit Python runtime [README](https://github.com/open-runtimes/open-runtimes/tree/main/openruntimes/python:v2-3.10).
4. Run the cURL function to send request.
>TinyPNG Curl Example (Supports only API_KEY in Environment Variables)
```bash
curl http://localhost:3000/ -H "X-Internal-Challenge: secret-key" -H "Content-Type: application/json" -d '{"payload": {"provider": "tinypng", "image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdj+L+U4T8ABu8CpCYJ1DQAAAAASUVORK5CYII="}, "variables": {"API_KEY": "<YOUR_API_KEY>"}}'
```
>KrakenIO Curl Example (Supports API_KEY and SECRET_API_KEY in Environment Variables)
```bash
curl http://localhost:3000/ -H "X-Internal-Challenge: secret-key" -H "Content-Type: application/json" -d '{"payload": {"provider": "krakenio", "image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdj+L+U4T8ABu8CpCYJ1DQAAAAASUVORK5CYII="}, "variables": {"API_KEY": "<YOUR_API_KEY>", "SECRET_API_KEY": "<YOUR_SECRET_API_KEY>"}}'
```
## 📝 Notes
- This function is designed for use with Appwrite Cloud Functions. You can learn more about it in [Appwrite docs](https://appwrite.io/docs/functions).
- This example is compatible with Python 3.10. Other versions may work but are not guaranteed to work as they haven't been tested.
147 changes: 147 additions & 0 deletions python/compress-image/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
""" Compress image function using Tinypng and Krakenio API."""
import base64
import json
import tinify
import requests

KRAKEN_API_ENDPOINT = "https://api.kraken.io/v1/upload"
KRAKEN_USER_AGENT = (
"Mozilla/5.0 (Windows NT 6.1; Win64; x64)AppleWebKit/"
"537.36(KHTML, like Gecko)Chrome/40.0.2214.85 Safari/537.36"
)


def implement_krakenio(variables):
"""
Implements image optimization using the Kraken.io API.

Input:
variables (dict): A dictionary containing the
required variables for optimization.
Returns:
optimized_image (bytes): decoded optimized image.
Raises:
raise_for_status (method): raise an HTTPError if the HTTP request
returned an unsuccessful status code.
"""
# Headers for post request
headers = {"User-Agent": KRAKEN_USER_AGENT}
# Image that we will pass in
files = {"file": variables["decoded_image"]}
# Parameters for post request
params = {
"auth": {
"api_key": variables["api_key"],
"api_secret": variables["api_secret_key"]
},
"wait": True, # Optional: Wait for the optimization to complete.
"dev": False, # Optional: Set to false to enter user mode.
}
response = requests.post(
url=KRAKEN_API_ENDPOINT,
headers=headers,
files=files,
data={"data": json.dumps(params)},
timeout=10,
)
# Check status code of response
response.raise_for_status()
data = response.json()
# Response unsuccessful, raise error
if not data["success"]:
raise ValueError("KrakenIO was not able to compress image.")
# Response successful, parse the response
optimized_url = data["kraked_url"]
optimized_image = requests.get(optimized_url, timeout=10).content
return optimized_image


def implement_tinypng(variables):
"""
Implements image optimization using the Tinypng API.

Input:
variables (dict): A dictionary containing the required variables
for optimization. Includes api_key and decoded_image.
Returns:
tinify.from_buffer().tobuffer() (bytes): decoded optimized image.
Raises:
tinify.error (method): raised if tinify fails to compress image.
"""
tinify.key = variables["api_key"]
return tinify.from_buffer(variables["decoded_image"]).to_buffer()


def validate_request(req):
"""
Validates the request and extracts the necessary information.

Input:
req (json): The request object containing the payload and variables.
Returns:
result (dict): Contains the validated request information.
Raises:
ValueError: If any required value is missing or invalid.
"""
# Check if payload is empty
if not req.payload:
raise ValueError("Missing payload.")
# Accessing provider from payload
if not req.payload.get("provider"):
raise ValueError("Missing provider.")
# Check if payload is not empty
if not req.variables:
raise ValueError("Missing variables.")
# Accessing api_key from variables
if not req.variables.get("API_KEY"):
raise ValueError("Missing API_KEY.")
# Accessing encoded image from payload
if not req.payload.get("image"):
raise ValueError("Missing encoding image.")
result = {
"provider": req.payload.get("provider").lower(),
"api_key": req.variables.get("API_KEY"),
"decoded_image": base64.b64decode(req.payload.get("image")),
}
# Get secret key
if req.payload.get("provider") == "krakenio":
if not req.variables.get("SECRET_API_KEY"):
raise ValueError("Missing api secret key.")
result["api_secret_key"] = req.variables.get("SECRET_API_KEY")
return result


def main(req, res):
"""
The main function that runs validate_request and calls IMPLEMENTATIONS.

Input:
req (json): The request object.
res (json): The response object.

Returns:
res (json): A JSON response containing the optimization results.
"""
try:
variables = validate_request(req)
except (ValueError) as value_error:
return res.json({
"success": False,
"error": f"{value_error}",
})
try:
if variables["provider"] == "tinypng":
optimized_image = implement_tinypng(variables)
elif variables["provider"] == "krakenio":
optimized_image = implement_krakenio(variables)
else:
raise ValueError("Invalid provider.")
except Exception as error:
return res.json({
"success": False,
"error": f"{type(error).__name__}: {error}",
})
return res.json({
"success": True,
"image": base64.b64encode(optimized_image).decode(),
})
3 changes: 3 additions & 0 deletions python/compress-image/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tinify==1.6.0
requests==2.31.0
parameterized==0.9.0
8 changes: 8 additions & 0 deletions python/compress-image/secret.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'''
API Key for tinyPNG and KrakenIO are stored here

You should be in python/compress-image directory to run test_main.py
'''
API_KEY_TINYPNG = None
API_KEY_KRAKENIO = None
SECRET_API_KEY_KRAKENIO = None
Binary file added python/compress-image/test/1kb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions python/compress-image/test/1kb_result_encoded_krakenio.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAIAAAAP9fodAAABz0lEQVR42mP4TxZgoEjbtwMH3nV1vW1u/rh06d/PnyGCa8+8Kll1C4gWHX2GRdvLwsLHnp4PbWweWls/dnF5Ghr6+9kz155zhg0nVCqOApF+3XEgF0Xb1337gEofGBoioyN1faq5uyQLDyEjZDsZ3rS2PrK2RtO2xz9eN3sLmrbU+VcR2l7X1DyytETTdtA9RD9rE5q20GkXEdo+zp//yN4eTdvSmDSN3B3SERNVPdKdfHyT4ryq67IWrl6F0Pbn3bsnPj7Ievb52UT1hMlahqjomWqpKeioydoZyMQ5Sb9faPrjRCsiJH/dvftuwoSHyQlnA1yXhJkHdTjolFrIYYCV6VyfFxn+fnwIEW9AO0+e2Z6zINF5kpPhXBdlLy1MbZmuwh+nS/043Y2SSvY+OOK7Jl5/ngsQKdmpY2oLtRb7OEX4x+EqFG2nn19M2FoI0aYarIuprSZA8NNsxV+XZqNo+/Dj06QzcyHadDrt0PT4molfahb+tiv93+cn6En50ccncy8uz9hRHrohzbM5xCvc1c3eyMlMLc5N+Wi7wff9hX/eXseeA4B2Xn97++yLSxdfXjtx6dSp44eObl9y5cDSP0+P/v1wj0oZZ3BrAwARK/tUoZKzbgAAAABJRU5ErkJggg==
1 change: 1 addition & 0 deletions python/compress-image/test/1kb_result_encoded_tinypng.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAMAAAC3SZ14AAABLFBMVEX///8eHh7gMTEZccIvnkTiOjrw+PL87u6ixeb+8uF0qtrM6NJFjM4wf8gkeMX85MT2v78mbbkuarPujY3thITkSkrjRETiPT1AMyFeRB8rJh7ymR398/OtzOr75ubi8eT63d3X7dv87dfR6dZln9VVltHI0sq94MTKx8LJzLehq6qd0afUwKXxpaXFt6Txn5+SzJ360psbWJHEfo74y4v4yIXrfHz4w3pouHf3v3FgtXHpcXF7fmr2umclSGdVsGalXGZsoGHhY2BNrF+8T19ajFblVVVJg1TBR1QdOVRCTE1iXkpKV0XNUEUxnURDkkJSiEAsiT6+TjwsdTikVjfvoTUkLjVGPzLzojHFhzAlUy0jSioiPidNNhdVOxbxlBPSgxNpRRN8UBGpZwp9EYrKAAAAs0lEQVQY063KRXYCQBAA0UkyBHd3d3d3d3eH+9+B1z0cgAW1/K/Ip8mUqrCJELNAwHtLUa3laDoWkVgiEYvYo/mFkvI/CD8VB6mlQHIBZRj1GLmBolqkmjxRGQxLASCuGmTR7m8MBp1+nQazKRvj1Wz5gz13Uvz89enkyOhuTBHI26T0xOhyzSIJC5QeGD2McaT/GKVzlPOtzCeYNZirdkdbnX6fdyDAZxc6Pb5ISArPN3sB8REVVO/DWBYAAAAASUVORK5CYII=
Loading