Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
hemansah committed Apr 13, 2024
0 parents commit 5ec42bc
Show file tree
Hide file tree
Showing 14 changed files with 495 additions and 0 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/publish-to-pypi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Upload Python Package

on:
release:
types: [published]

permissions:
contents: read

jobs:
deploy:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Publish package on PyPi
uses: pypa/gh-action-pypi-publish@master
with:
password: ${{ secrets.PYPI_API_TOKEN }}

40 changes: 40 additions & 0 deletions .github/workflows/publish-to-test-pypi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Upload Python Package

on:
release:
types: [published]

permissions:
contents: read

jobs:
deploy:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Publish package on TestPyPi
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN_TEST }}
repository_url: https://test.pypi.org/legacy/
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
test.py
venv/
dist/
resmushit.egg-info/
__pycache__/
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Changelog

## [1.0.0] - 2024-04-13

### Added
- Initial Release
9 changes: 9 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
MIT License

Copyright (c) 2018 reSmush.it

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
graft resmushit
include pyproject.toml
include setup.py
global-exclude *.py[cod]
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# reSmush.it Image Optimizer Python package



## Project Description

Python3 wrapper for resmush.it API to optimize image file size.



[resmush.it](https://resmush.it/) Image Optimizer allows you to use free Image optimization based on reSmush.it API.

reSmush.it provides image size reduction based on several algorithms. The API accept JPG, PNG and GIF files up to 5MB.



## Installation



1. Install using pip3:



`$ pip3 install resmushit`



## Usage

```
import resmushit
# Save image to current directory
resmushit.from_path(image_path='image.png', quality=95)
# Save image to preferred directory
resmushit.from_path(image_path='image.png', quality=95, output_dir="output/")
# return image bytes
_bytes = resmushit.from_url(image_url="https://ps.w.org/resmushit-image-optimizer/assets/icon-128x128.png", quality=95, save=False)
```



## Options

```
image_url (str): The url of image.
image_path (str): The path of image
quality (int): Quality at which image is going to be optimized.
output_dir (str): Location for output image.
preserve_exif (bool): Preserve EXIF data in the file after optimization.
preserve_filename (bool): Optimized image will prefix 'optimized-' before image name.
quiet_mode (bool): Run in quiet mode when True
save (bool): When True, saves image in directory.
When False, returns bytes of image.
```

7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[build-system]
requires = [
"setuptools>=42",
"wheel",
]

build-backend = "setuptools.build_meta"
14 changes: 14 additions & 0 deletions resmushit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from .resmushit import Resmushit
from .exceptions import ImageURLNotFoundException, ImagePathNotFoundException

def from_url(image_url=None, quality=92, output_dir=".", preserve_exif=False, preserve_filename=False, quiet_mode=False, notime=True, save=True):
if image_url:
resmushit = Resmushit(image_url=image_url, image_path=None, quality=quality, output_dir=output_dir, preserve_exif=preserve_exif, preserve_filename=preserve_filename, quiet_mode=quiet_mode)
return resmushit.optimize(save)
raise ImageURLNotFoundException("Please provide an image url")

def from_path(image_path=None, quality=92, output_dir=".", preserve_exif=False, preserve_filename=False, quiet_mode=False, notime=True, save=True):
if image_path:
resmushit = Resmushit(image_path=image_path, image_url=None, quality=quality, output_dir=output_dir, preserve_exif=preserve_exif, preserve_filename=preserve_filename, quiet_mode=quiet_mode)
return resmushit.optimize(save)
raise ImagePathNotFoundException("Please provide an image path")
14 changes: 14 additions & 0 deletions resmushit/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class FileTooLargeError(Exception):
pass


class InvalidImageExtensionError(Exception):
pass


class ImageURLNotFoundException(Exception):
pass


class ImagePathNotFoundException(Exception):
pass
25 changes: 25 additions & 0 deletions resmushit/log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from colorama import Fore, Style, init
from datetime import datetime
from sys import stdout

init()


class Log:
__colors = {
"red": Fore.RED,
"green": Fore.GREEN,
"white": Fore.WHITE,
"blue": Fore.BLUE,
}

def __init__(self, quiet_mode) -> None:
self.quiet_mode = quiet_mode

def log(self, message, color="white"):
if not self.quiet_mode:
print(
Log.__colors.get(color, "white")
+ f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} {message}"
+ Style.RESET_ALL
)
124 changes: 124 additions & 0 deletions resmushit/resmushit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import json
import os
from urllib3 import PoolManager
from .log import Log
from .validator import Validator


class Resmushit:
"""
Wrapper for Resmush.it API
Args:
image_url (str): The url of image.
image_path (str): The path of image
quality (int): Quality at which image is going to be optimized.
output_dir (str): Location for output image.
preserve_exif (bool): Preserve EXIF data in the file after optimization.
preserve_filename (bool): Optimized image will prefix 'optimized-' before image name.
quiet_mode (bool): Run in quiet mode when True
Returns:
bytes or None: if save=True, image is saved into mentioned output directory
if save=False, image bytes are returned
## Usage:
`import resmushit`
`resmushit.from_path(image_path='image.png', quality=95)`
`buffer = resmushit.from_url(image_url="https://ps.w.org/resmushit-image-optimizer/assets/icon-128x128.png", quality=95, save=False)`
"""

__MAX_FILESIZE = 5 * 1024 * 1024

def __init__(
self,
image_url: str = None,
image_path: str = None,
quality: int = 92,
output_dir: str = ".",
preserve_exif: bool = False,
preserve_filename: bool = False,
quiet_mode: bool = False,
) -> None:
if image_path is not None and image_url is not None:
raise ValueError("Either image_path or image_url should be passed")
elif image_url is not None:
self.image_url = image_url
self.image_path = None
elif image_path is not None:
self.image_path = image_path
self.image_url = None
else:
raise ValueError("image_path or image_url is required")

self.quality = quality
self.output_dir = output_dir
self.preserve_exif = preserve_exif
self.preserve_filename = preserve_filename
self.quiet_mode = quiet_mode
self.__API_URL: str = "http://api.resmush.it"
self._response = None
self.__logger = Log(quiet_mode=quiet_mode)

def __call_api(self):
try:
self.__logger.log(
message=f"Initializing image optimization with quality factor: {self.quality}%",
color="blue",
)
self.__logger.log(message=f"Sending picture {self.filename} to api...")

http = PoolManager()
r = http.request(
"POST",
self.__API_URL + f"/?qlty={self.quality}&exif={self.preserve_exif}",
fields={"files": ("image.png", self.imagebytes)},
)
self._response = json.loads(r.data.decode("utf-8"))
self.__logger.log(
message=f"File optimized by {self._response.get('percent',0)}% (from {self._response.get('src_size',0)//1024}KB to {self._response.get('dest_size',0)//1024}KB). Retrieving...",
color="green",
)
except Exception as e:
raise Exception(f"Error Occurred: {str(e)}")

def __get_dest_url(self):
return self._response.get("dest")

def __download_image(self, dest_url):
try:
http = PoolManager()
r = http.request("GET", dest_url)
return r.data
except Exception as e:
raise Exception(f"{e}")

def __save_image(self, imagebytes):
with open(
os.path.join(
self.output_dir,
f"{'' if self.preserve_filename else 'optimized-'}{self.filename}"
+ f"{self.extension}",
),
"wb",
) as f:
f.write(imagebytes)

def optimize(self, save: bool = True):
self.path, self.type, self.imagebytes, self.filename, self.extension = (
Validator(
path=self.image_url or self.image_path,
max_file_size=Resmushit.__MAX_FILESIZE,
).validate()
)
self.__logger.log(f"Processing: {self.filename}")
self.__call_api()

dest_url = self.__get_dest_url()
optimized_imagebytes = self.__download_image(dest_url=dest_url)
if save:
self.__save_image(imagebytes=optimized_imagebytes)
else:
return optimized_imagebytes
Loading

0 comments on commit 5ec42bc

Please sign in to comment.