From fe59ec07cc9881ea1d1389cb396b74b7f93b391a Mon Sep 17 00:00:00 2001 From: ajaysi Date: Thu, 15 Aug 2024 09:47:12 +0530 Subject: [PATCH] Backlinking tool & Img optimization, PIL & Tinify API --- README.md | 12 +- .../optimize_images_for_upload.py | 190 ++++++++++++++++++ .../wordpress_blog_uploader.py | 0 lib/utils/alwrity_utils.py | 6 +- lib/utils/optimize_images_for_upload.py | 112 ----------- requirements.txt | 2 +- 6 files changed, 206 insertions(+), 116 deletions(-) create mode 100644 lib/ai_seo_tools/optimize_images_for_upload.py rename lib/{utils => integrations}/wordpress_blog_uploader.py (100%) delete mode 100644 lib/utils/optimize_images_for_upload.py 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, ![AI writers](https://www.alwrity.com/ai-writing-tools) & ![Social media content generation](https://www.alwrity.com/ai-youtube-script-writer) & ![AI Copywriting](https://www.alwrity.com/ai-copywriting-formula-generator). 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. -![](https://github.com/AJaySi/AI-Writer/blob/main/lib/workspace/keyword_blog.gif) +- ![Generate Content Calender for Months](https://www.alwrity.com/post/automating-content-calendars-with-ai-agents) +- ![Specilized AI writers for every need & platform](https://github.com/AJaySi/AI-Writer/wiki/Features-of-ALwrity-AI-writer) +- ![ALwrity AI SEO Tools](https://github.com/AJaySi/AI-Writer/wiki/ALwrity-AI-SEO-Tools) +- ![ALwrity Web Researcher](https://github.com/AJaySi/AI-Writer/wiki/Alwrity-AI-Web-Research-Details-for-content-writing) +- RoadMap - Coming Soon.. + +--- +> ![](https://github.com/AJaySi/AI-Writer/blob/main/lib/workspace/keyword_blog.gif) +--- ### 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