diff --git a/README.md b/README.md
index e431057c..78c01bb3 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-> [!NOTE]
ALwrity is in Work in Progress (WIP). All in One Content platform
+> [!NOTE] 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.
# 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] You will find AI content writing tools, which are Free & No-Signup.
diff --git a/lib/ai_seo_tools/optimize_images_for_upload.py b/lib/ai_seo_tools/optimize_images_for_upload.py
new file mode 100644
index 00000000..31c3bc95
--- /dev/null
+++ b/lib/ai_seo_tools/optimize_images_for_upload.py
@@ -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}|{file}:{line}:{function}| {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"
+ )
diff --git a/lib/utils/wordpress_blog_uploader.py b/lib/integrations/wordpress_blog_uploader.py
similarity index 100%
rename from lib/utils/wordpress_blog_uploader.py
rename to lib/integrations/wordpress_blog_uploader.py
diff --git a/lib/utils/alwrity_utils.py b/lib/utils/alwrity_utils.py
index b21c419d..231e74d0 100644
--- a/lib/utils/alwrity_utils.py
+++ b/lib/utils/alwrity_utils.py
@@ -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()
diff --git a/lib/utils/optimize_images_for_upload.py b/lib/utils/optimize_images_for_upload.py
deleted file mode 100644
index 16b938f4..00000000
--- a/lib/utils/optimize_images_for_upload.py
+++ /dev/null
@@ -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}|{file}:{line}:{function}| {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
diff --git a/requirements.txt b/requirements.txt
index f5c2d471..9c9b4e29 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -35,4 +35,4 @@ pandas_ta
firecrawl-py
gTTS
streamlit-mic-recorder
-
+tinify