File size: 7,458 Bytes
bd0da6c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
from typing import Any
import os
from PIL import Image
import io
import base64
import requests
import time

#Gradio for the hackaton:
import gradio as gr

# we used   uv add mcp[cli] httpx   to get these:
import httpx
from mcp.server.fastmcp import FastMCP

# Initialize FastMCP server
mcp = FastMCP("linkedin-image-processor")

# Constants
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))

#let's add our helper functions:

async def flux_kontext_edit_image(image: Image.Image, prompt: str) -> Image.Image:
    """Use Flux Kontext API to edit an image based on a prompt
    
    Args:
        image: PIL Image to edit
        prompt: Text description of what to edit
        
    Returns:
        Image.Image: Edited image from Flux Kontext
    """
    try:
        # Encode image to base64
        buffered = io.BytesIO()
        image.save(buffered, format="JPEG")
        img_str = base64.b64encode(buffered.getvalue()).decode()
        
        # Make request to Flux Kontext API
        response = requests.post(
            'https://api.bfl.ai/v1/flux-kontext-pro',
            headers={
                'accept': 'application/json',
                'x-key': os.environ.get("BFL_API_KEY"),
                'Content-Type': 'application/json',
            },
            json={
                'prompt': prompt,
                'input_image': img_str,
            },
        )
        
        if response.status_code != 200:
            print(f"API request failed: {response.status_code}")
            return image
            
        request_data = response.json()
        request_id = request_data.get("id")
        
        if not request_id:
            print("No request ID received")
            return image
            
        # Poll for result (simplified polling)
        max_attempts = 30
        for attempt in range(max_attempts):
            time.sleep(2)
            
            result_response = requests.get(
                f'https://api.bfl.ai/v1/get_result?id={request_id}',
                headers={
                    'accept': 'application/json',
                    'x-key': os.environ.get("BFL_API_KEY"),
                }
            )
            
            if result_response.status_code == 200:
                result_data = result_response.json()
                
                if result_data.get("status") == "Ready":
                    image_url = result_data.get("result", {}).get("sample")
                    if image_url:
                        # Download and return the edited image
                        img_response = requests.get(image_url)
                        edited_image = Image.open(io.BytesIO(img_response.content))
                        return edited_image
                        
                elif result_data.get("status") == "Error":
                    print(f"Flux Kontext error: {result_data.get('result')}")
                    break
                    
        print("Flux Kontext processing timeout or failed")
        return image
        
    except Exception as e:
        print(f"Error with Flux Kontext API: {e}")
        return image

def process_linkedin_image(image) -> Image.Image:
    """Process an image for LinkedIn optimization using Flux Kontext
    
    Args:
        image: Input image file
        
    Returns:
        Image.Image: Processed image optimized for LinkedIn
    """
    if image is None:
        return None
    
    try:
        # Handle different input types
        if isinstance(image, str):
            img = Image.open(image)
        else:
            img = image
            
        # Define the fixed professional prompt
        professional_prompt = "Make the person wear a light blue blazer, make the background white and clean any noise in the foreground. make the hair more orderly. Keep the face of the person intact. keep the gender of the person intact. the image should always be a bust"
        
        # First, use Flux Kontext to enhance/edit the image
        import asyncio
        edited_img = asyncio.run(flux_kontext_edit_image(img, professional_prompt))
        
        # Then apply LinkedIn optimization
        target_width = 800
        target_height = 800
        
        # Calculate aspect ratio
        original_width, original_height = edited_img.size
        original_ratio = original_width / original_height
        target_ratio = target_width / target_height
        
        # Resize while maintaining aspect ratio
        if original_ratio > target_ratio:
            new_width = target_width
            new_height = int(target_width / original_ratio)
        else:
            new_height = target_height
            new_width = int(target_height * original_ratio)
        
        # Resize the image
        img_resized = edited_img.resize((new_width, new_height), Image.Resampling.LANCZOS)
        
        # Create a new image with LinkedIn dimensions and white background
        linkedin_img = Image.new('RGB', (target_width, target_height), 'white')
        
        # Calculate position to center the resized image
        x = (target_width - new_width) // 2
        y = (target_height - new_height) // 2
        
        # Paste the resized image onto the LinkedIn-sized canvas
        linkedin_img.paste(img_resized, (x, y))
        
        return linkedin_img
        
    except Exception as e:
        print(f"Error processing image: {e}")
        return image if image else None

@mcp.tool()
async def create_professional_linkedin_headshot(image_url: str) -> str:
    """Transform any photo into a professional LinkedIn headshot using AI.
    
    Automatically adds professional business attire (light blue blazer), creates a clean white 
    background, tidies hair, removes noise, and formats as an 800x800 centered bust shot while 
    preserving facial features and gender. Perfect for professional headshots, profile pictures, 
    business photos, and LinkedIn optimization.
    
    Args:
        image_url: HTTP/HTTPS URL to the input image file (JPEG, PNG supported)
        
    Returns:
        str: Success message or error description
    """
    try:
        processed_img = process_linkedin_image(image_url)
        if processed_img:
            return "Professional LinkedIn headshot created successfully - added business attire, clean background, and professional formatting"
        else:
            return "Failed to process image for LinkedIn optimization"
    except Exception as e:
        return f"Error creating professional headshot: {str(e)}"

@mcp.resource("config://linkedin-optimizer")
async def linkedin_optimizer_resource():
    """LinkedIn image optimization resource
    Provides optimal dimensions and processing for LinkedIn posts
    """
    return {
        "name": "LinkedIn Image Optimizer",
        "description": "Optimizes images for LinkedIn posts",
        "recommended_dimensions": "800x800 pixels",
        "supported_formats": ["JPEG", "PNG", "GIF"],
        "max_file_size": "5MB"
    }

demo = gr.Interface(
    fn=process_linkedin_image,
    inputs=gr.Image(type="pil", label="Upload Your Photo"),
    outputs=gr.Image(type="pil", label="Professional LinkedIn Photo"),
    title="Professional LinkedIn Photo Generator",
    description="Upload a photo and automatically transform it into a professional LinkedIn profile picture."
)

if __name__ == "__main__":
    # Initialize and run the server
    demo.launch(mcp_server=True)