Backlinking tool & Img optimization, PIL & Tinify API
This commit is contained in:
12
README.md
12
README.md
@@ -1,4 +1,4 @@
|
||||
> [!NOTE] <p><em>ALwrity is in Work in Progress (WIP). All in One Content platform
|
||||
> [!NOTE] <p><em>ALwrity is All in One Content platform for content writers, without AI tooling knowledge. WIP.
|
||||
Not just AI Writers, ALwrity is a complete content life cycle platform with **Content planning & Research, Personlized Content Generation, Content SEO audits, Content Scheduling/publishing, Analytics & monitoring & more**. We are busy building blocks for each phase of content life cycle with AI. We'd love your feedback or contributions.</em></p>
|
||||
|
||||
# How to ALwrity - Getting Started
|
||||
@@ -6,7 +6,15 @@ Not just AI Writers, ALwrity is a complete content life cycle platform with **Co
|
||||
ALwrity assists content creators and digital marketers in keyword web research,  &  & .
|
||||
Our toolkit integrates **(OpenAI, Gemini, Anthropic)** AI models for text generation, image creation **(Stability.ai), STT(whisper, AssemblyAI)** and AI Web research **(Tavily AI, exa AI, Serper.dev)**, streamlining your content creation pipeline and ensuring high-quality output with minimal effort.
|
||||
|
||||

|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- RoadMap - Coming Soon..
|
||||
|
||||
---
|
||||
> 
|
||||
---
|
||||
|
||||
### Option 1: Get Started Now, [Visit alwrity.com](https://www.alwrity.com/ai-writing-tools)
|
||||
> [!NOTE] <p>You will find AI content writing tools, which are Free & No-Signup.
|
||||
|
||||
190
lib/ai_seo_tools/optimize_images_for_upload.py
Normal file
190
lib/ai_seo_tools/optimize_images_for_upload.py
Normal file
@@ -0,0 +1,190 @@
|
||||
import os
|
||||
import sys
|
||||
import tinify
|
||||
from PIL import Image
|
||||
from loguru import logger
|
||||
from dotenv import load_dotenv
|
||||
import streamlit as st
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Set Tinyfy API key from environment variable
|
||||
tinify_key = os.getenv('TINIFY_API_KEY')
|
||||
if tinify_key:
|
||||
tinify.key = tinify_key
|
||||
|
||||
# Configure logger
|
||||
logger.remove()
|
||||
logger.add(
|
||||
sys.stdout,
|
||||
colorize=True,
|
||||
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}"
|
||||
)
|
||||
|
||||
def compress_image(image, quality=45, resize=None, preserve_exif=False):
|
||||
"""
|
||||
Compress and optionally resize an image.
|
||||
|
||||
Args:
|
||||
image (PIL.Image): Image object to compress.
|
||||
quality (int): Quality of the output image (1-100).
|
||||
resize (tuple): Tuple (width, height) to resize the image.
|
||||
preserve_exif (bool): Preserve EXIF data if True.
|
||||
|
||||
Returns:
|
||||
PIL.Image: The compressed and resized image object.
|
||||
"""
|
||||
try:
|
||||
# Ensure image is in a compatible mode for JPEG/WebP
|
||||
if image.mode == 'RGBA':
|
||||
logger.info("Converting RGBA image to RGB.")
|
||||
image = image.convert('RGB')
|
||||
|
||||
exif = image.info.get('exif') if preserve_exif and 'exif' in image.info else None
|
||||
|
||||
# Resize image if needed
|
||||
if resize:
|
||||
image = image.resize(resize, Image.LANCZOS)
|
||||
logger.info(f"Resized image to {resize}")
|
||||
|
||||
# Save compressed image
|
||||
try:
|
||||
logger.info("Attempting to save the compressed image with EXIF data (if any).")
|
||||
image.save("temp.jpg", optimize=True, quality=quality, exif=exif)
|
||||
except Exception as exif_error:
|
||||
logger.warning(f"Error saving image with EXIF: {exif_error}. Saving without EXIF.")
|
||||
image.save("temp.jpg", optimize=True, quality=quality)
|
||||
|
||||
logger.info("Image compression successful.")
|
||||
return Image.open("temp.jpg")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error compressing image: {e}")
|
||||
st.error("Failed to compress the image. Please try again.")
|
||||
return None
|
||||
|
||||
def convert_to_webp(image, image_path):
|
||||
"""
|
||||
Convert an image to WebP format.
|
||||
|
||||
Args:
|
||||
image (PIL.Image): Image object to convert.
|
||||
image_path (str): Path to save the WebP image.
|
||||
|
||||
Returns:
|
||||
str: Path to the WebP image.
|
||||
"""
|
||||
try:
|
||||
webp_path = os.path.splitext(image_path)[0] + '.webp'
|
||||
image.save(webp_path, 'WEBP', quality=80, method=6)
|
||||
return webp_path
|
||||
except Exception as e:
|
||||
logger.error(f"Error converting image to WebP: {e}")
|
||||
st.error("Failed to convert the image to WebP format. Please try again.")
|
||||
return None
|
||||
|
||||
def compress_image_tinyfy(image_path):
|
||||
"""
|
||||
Compress an image using Tinyfy API.
|
||||
|
||||
Args:
|
||||
image_path (str): Path to the image to be compressed.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
try:
|
||||
if not tinify.key:
|
||||
logger.warning("Tinyfy API key is not set. Skipping Tinyfy compression.")
|
||||
return
|
||||
|
||||
source = tinify.from_file(image_path)
|
||||
source.to_file(image_path)
|
||||
logger.info("Tinyfy compression successful.")
|
||||
except Exception as e:
|
||||
logger.error(f"Error during Tinyfy compression: {e}")
|
||||
st.warning("Tinyfy compression failed. Ensure the API key is set.")
|
||||
|
||||
def optimize_image(image, image_path, quality, resize, preserve_exif):
|
||||
"""
|
||||
Optimize the image by compressing and converting it to WebP, with optional Tinyfy compression.
|
||||
|
||||
Args:
|
||||
image (PIL.Image): The original image.
|
||||
image_path (str): The path to the image file.
|
||||
quality (int): Quality level for compression.
|
||||
resize (tuple): Dimensions to resize the image.
|
||||
preserve_exif (bool): Whether to preserve EXIF data.
|
||||
|
||||
Returns:
|
||||
str: Path to the optimized WebP image, or None if failed.
|
||||
"""
|
||||
logger.info("Starting image optimization process...")
|
||||
|
||||
# Compress the image using Pillow
|
||||
compressed_image = compress_image(image, quality, resize, preserve_exif)
|
||||
if compressed_image is None:
|
||||
return None
|
||||
|
||||
# Convert image to WebP format
|
||||
webp_path = convert_to_webp(compressed_image, image_path)
|
||||
if webp_path is None:
|
||||
return None
|
||||
|
||||
# Optionally compress the WebP image using Tinyfy API
|
||||
if tinify.key:
|
||||
compress_image_tinyfy(webp_path)
|
||||
else:
|
||||
logger.info("Tinyfy key not provided, skipping Tinyfy compression.")
|
||||
|
||||
return webp_path
|
||||
|
||||
def main_img_optimizer():
|
||||
st.title("ALwrity Image Optimizer")
|
||||
st.markdown("## Upload an image to optimize its size and format.")
|
||||
|
||||
# API Key Input (Optional)
|
||||
input_tinify_key = st.text_input("Optional: Enter your Tinyfy API Key")
|
||||
if input_tinify_key:
|
||||
tinify.key = input_tinify_key
|
||||
|
||||
# File Upload
|
||||
uploaded_file = st.file_uploader("Upload an image", type=['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'])
|
||||
|
||||
if uploaded_file:
|
||||
image = Image.open(uploaded_file)
|
||||
st.image(image, caption="Original Image", use_column_width=True)
|
||||
|
||||
# Image Compression Options
|
||||
quality = st.slider("Compression Quality", 1, 100, 45)
|
||||
preserve_exif = st.checkbox("Preserve EXIF Data", value=False)
|
||||
resize = st.checkbox("Resize Image")
|
||||
|
||||
if resize:
|
||||
width = st.number_input("Width", value=image.width)
|
||||
height = st.number_input("Height", value=image.height)
|
||||
resize_dims = (width, height)
|
||||
else:
|
||||
resize_dims = None
|
||||
|
||||
# Optimize Image
|
||||
if st.button("Optimize Image"):
|
||||
with st.spinner("Optimizing..."):
|
||||
if tinify.key:
|
||||
st.info("Tinyfy compression will be applied.")
|
||||
|
||||
webp_path = optimize_image(image, uploaded_file.name, quality, resize_dims, preserve_exif)
|
||||
|
||||
if webp_path:
|
||||
st.image(webp_path, caption="Optimized Image (WebP)", use_column_width=True)
|
||||
st.success("Image optimization completed!")
|
||||
|
||||
# Provide download link
|
||||
with open(webp_path, "rb") as file:
|
||||
st.download_button(
|
||||
label="Download Optimized Image",
|
||||
data=file,
|
||||
file_name=os.path.basename(webp_path),
|
||||
mime="image/webp"
|
||||
)
|
||||
@@ -43,6 +43,7 @@ from lib.ai_seo_tools.content_title_generator import ai_title_generator
|
||||
from lib.ai_seo_tools.meta_desc_generator import metadesc_generator_main
|
||||
from lib.ai_seo_tools.image_alt_text_generator import alt_text_gen
|
||||
from lib.ai_seo_tools.opengraph_generator import og_tag_generator
|
||||
from lib.ai_seo_tools.optimize_images_for_upload import main_img_optimizer
|
||||
from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
|
||||
from lib.content_planning_calender.content_planning_agents_alwrity_crew import ai_agents_planner
|
||||
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||
@@ -127,7 +128,8 @@ def ai_seo_tools():
|
||||
"Generate SEO optimized Blog Titles",
|
||||
"Generate Meta Description for SEO",
|
||||
"Generate Image Alt Text",
|
||||
"Generate OpenGraph Tags"
|
||||
"Generate OpenGraph Tags",
|
||||
"Optimize/Resize Image"
|
||||
]
|
||||
choice = st.selectbox("**👇Select AI SEO Tool:**", options, index=0, format_func=lambda x: f"📝 {x}")
|
||||
|
||||
@@ -141,6 +143,8 @@ def ai_seo_tools():
|
||||
alt_text_gen()
|
||||
elif choice == "Generate OpenGraph Tags":
|
||||
og_tag_generator()
|
||||
elif choice == "Optimize/Resize Image":
|
||||
main_img_optimizer()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import sys
|
||||
import os
|
||||
import tinify
|
||||
from PIL import Image
|
||||
from loguru import logger
|
||||
from tqdm import tqdm
|
||||
from dotenv import load_dotenv
|
||||
|
||||
#default directory for .env file is the current directory
|
||||
#if you set .env in different directory, put the directory address load_dotenv("directory_of_.env)
|
||||
load_dotenv()
|
||||
|
||||
# Retrieve Tinyfy API key from environment variable
|
||||
tinify.key = os.getenv('TINIFY_API_KEY')
|
||||
|
||||
# Configure logger
|
||||
logger.remove()
|
||||
logger.add(sys.stdout, colorize=True, format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}")
|
||||
|
||||
def compress_image(image_path, quality=45, resize=None, preserve_exif=False):
|
||||
"""
|
||||
Compress and optionally resize an image, and overwrite the original image.
|
||||
|
||||
Args:
|
||||
image_path (str): Path to the original image.
|
||||
quality (int): Quality of the output image (1-100).
|
||||
resize (tuple): Tuple (width, height) to resize image.
|
||||
preserve_exif (bool): Preserve EXIF data if True.
|
||||
"""
|
||||
if not os.path.exists(image_path):
|
||||
logger.error(f"Image path does not exist: {image_path}")
|
||||
return
|
||||
|
||||
original_size = os.path.getsize(image_path)
|
||||
try:
|
||||
with Image.open(image_path) as img:
|
||||
img_format = img.format
|
||||
exif = img.info['exif'] if preserve_exif and 'exif' in img.info else None
|
||||
|
||||
if resize:
|
||||
img = img.resize(resize, Image.ANTIALIAS)
|
||||
|
||||
img.save(image_path, format=img_format, quality=quality, optimize=True, exif=exif)
|
||||
|
||||
compressed_size = os.path.getsize(image_path)
|
||||
reduction = (1 - (compressed_size / original_size)) * 100
|
||||
logger.info(f"Compressed {image_path}, Reduction: {reduction:.2f}%")
|
||||
except Exception as e:
|
||||
logger.error(f"Error compressing image {image_path}: {e}")
|
||||
|
||||
def is_image_file(filename):
|
||||
"""
|
||||
Check if a file is an image based on its extension.
|
||||
|
||||
Args:
|
||||
filename (str): Name of the file to check.
|
||||
|
||||
Returns:
|
||||
bool: True if the file is an image, False otherwise.
|
||||
"""
|
||||
valid_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
|
||||
return any(filename.lower().endswith(ext) for ext in valid_extensions)
|
||||
|
||||
|
||||
def convert_to_webp(image_path):
|
||||
"""
|
||||
Convert an image to WebP format.
|
||||
|
||||
Args:
|
||||
image_path (str): Path to the original image.
|
||||
|
||||
Returns:
|
||||
str: Path to the WebP image.
|
||||
"""
|
||||
if not os.path.exists(image_path):
|
||||
logger.error(f"Image path does not exist: {image_path}")
|
||||
return
|
||||
|
||||
try:
|
||||
with Image.open(image_path) as img:
|
||||
webp_path = os.path.splitext(image_path)[0] + '.webp'
|
||||
img.save(webp_path, 'WEBP')
|
||||
logger.info(f"Converted {image_path} to WebP")
|
||||
return webp_path
|
||||
except Exception as e:
|
||||
logger.error(f"Error converting image to WebP: {e}")
|
||||
|
||||
|
||||
def compress_image_tinyfy(image_path):
|
||||
"""
|
||||
Compress the image using Tinyfy API.
|
||||
|
||||
Args:
|
||||
image_path (str): Path to the original image.
|
||||
"""
|
||||
if not os.path.exists(image_path):
|
||||
logger.error(f"Image path does not exist: {image_path}")
|
||||
return
|
||||
|
||||
try:
|
||||
source = tinify.from_file(image_path)
|
||||
source.to_file(image_path)
|
||||
logger.info(f"Compressed {image_path} using Tinyfy API")
|
||||
except tinify.Error as e:
|
||||
logger.error(f"Tinyfy API error: {e}")
|
||||
|
||||
|
||||
def optimize_image(image_path):
|
||||
image_path = convert_to_webp(image_path)
|
||||
compress_image_tinyfy(image_path)
|
||||
compress_image(image_path)
|
||||
return image_path
|
||||
@@ -35,4 +35,4 @@ pandas_ta
|
||||
firecrawl-py
|
||||
gTTS
|
||||
streamlit-mic-recorder
|
||||
|
||||
tinify
|
||||
|
||||
Reference in New Issue
Block a user