File size: 8,585 Bytes
c4b7f45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c5f009
 
b9e91eb
2c5f009
c4b7f45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
210
211
212
213
214
215
216
217
218
219
220
221
222
from flask import Flask, request, jsonify
import requests
import base64
import io
import json
from urllib.parse import unquote

app = Flask(__name__)

# Style configurations
STYLES = {
    'pixel': {
        'prompt': 'Turn this image into the Pixel style.',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/Pixel_lora_weights.safetensors'
    },
    'snoopy': {
        'prompt': 'Turn this image into the Snoopy style.',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/Snoopy_lora_weights.safetensors'
    },
    'jojo': {
        'prompt': 'Turn this image into the JoJo style.',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/Jojo_lora_weights.safetensors'
    },
    'clay': {
        'prompt': 'Turn this image into the Clay style.',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/Clay_Toy_lora_weights.safetensors'
    },
    'ghibli': {
        'prompt': 'Turn this image into the Ghibli style.',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/Ghibli_lora_weights.safetensors'
    },
    'american-cartoon': {
        'prompt': 'Turn this image into the American Cartoon style.',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/American_Cartoon_lora_weights.safetensors'
    },
    'lego': {
        'prompt': 'convert to lego style',
        'lora_url': 'https://huggingface.co/Owen777/Kontext-Style-Loras/resolve/main/LEGO_lora_weights.safetensors'
    },
    'broccoli-hair': {
        'prompt': 'Change hair to a broccoli haircut',
        'lora_url': 'https://huggingface.co/fal/Broccoli-Hair-Kontext-Dev-LoRA/resolve/main/broccoli-hair-kontext-dev-lora.safetensors'
    },
    'plushie': {
        'prompt': 'Convert to plushie style',
        'lora_url': 'https://huggingface.co/fal/Plushie-Kontext-Dev-LoRA/resolve/main/plushie-kontext-dev-lora.safetensors'
    },
    'wojak': {
        'prompt': 'Convert to wojak style drawing',
        'lora_url': 'https://huggingface.co/fal/Wojak-Kontext-Dev-LoRA/resolve/main/wojak-kontext-dev-lora.safetensors'
    },
    'upscalecompression': {
        'prompt': 'fix the jpeg compression',
        'lora_url': 'https://huggingface.co/fofr/flux-kontext-dev-jpeg-compression-fix-lora/resolve/main/flux-kontext-dev-jpeg-compression-fix-lora.safetensors'
    },
        'gfx': {
        'prompt': 'render this image into a gfx image, fill in the background perfect and edit the image to look like awesome graphic gfx image',
        'lora_url': 'https://huggingface.co/jerrrycans/gfx/resolve/main/flux-kontext-gfx-lora.safetensors'
    },
    'fluffy': {
        'prompt': 'make this object fluffy',
        'lora_url': None
    },
    'glass': {
        'prompt': 'make the character/object look like it was made out of glass, black background',
        'lora_url': None
    },
    'simpsons': {
        'prompt': 'convert to Simpsons cartoon style',
        'lora_url': None
    },
    'anime': {
        'prompt': 'convert to anime art style with large eyes and stylized features',
        'lora_url': None
    }
}

def upload_base64_image(base64_data):
    """Upload base64 image to jerrrycans-file.hf.space"""
    try:
        # Convert base64 to bytes
        header, data = base64_data.split(',', 1)
        image_data = base64.b64decode(data)
        
        # Upload to jerrrycans
        files = {'file': ('generated_image.png', io.BytesIO(image_data), 'image/png')}
        headers = {
            'Origin': 'https://jerrrycans-file.hf.space',
            'Referer': 'https://jerrrycans-file.hf.space/',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        }
        
        response = requests.post('https://jerrrycans-file.hf.space/upload', 
                               files=files, headers=headers)
        
        if response.status_code == 200:
            result = response.json()
            return f"https://jerrrycans-file.hf.space{result['url']}"
    except Exception as e:
        print(f"Upload error: {e}")
    return None

def get_style_config(style, custom_lora_url=None):
    """Get style configuration"""
    style_config = STYLES.get(style.lower())
    
    if style_config:
        return {
            'prompt': style_config['prompt'],
            'lora_url': custom_lora_url or style_config['lora_url']
        }
    else:
        # Custom prompt
        return {
            'prompt': style,
            'lora_url': custom_lora_url
        }

def process_stream_response(response):
    """Process streaming response from FAL API"""
    uploaded_images = []
    buffer = ''
    
    for chunk in response.iter_content(chunk_size=1024, decode_unicode=True):
        if chunk:
            buffer += chunk
            lines = buffer.split('\n')
            buffer = lines.pop()  # Keep incomplete line in buffer
            
            for line in lines:
                if line.startswith('data: ') and len(line) > 6:
                    try:
                        data_content = line[6:].strip()
                        if not data_content:
                            continue
                            
                        data = json.loads(data_content)
                        
                        # Check for progress or completed data with images
                        if (data.get('json', {}).get('type') in ['progress', 'completed'] and 
                            data.get('json', {}).get('data', {}).get('images')):
                            
                            for image in data['json']['data']['images']:
                                if image.get('url', '').startswith('data:image/'):
                                    uploaded_url = upload_base64_image(image['url'])
                                    if uploaded_url:
                                        uploaded_images.append(uploaded_url)
                    except:
                        continue
    
    return uploaded_images

@app.route('/api/transform', methods=['POST'])
def transform_image():
    try:
        # Get JSON data from request
        data = request.get_json()
        
        if not data:
            return jsonify({'success': False, 'error': 'No JSON data provided'}), 400
        
        # Extract required fields
        image_url = data.get('image_url')
        style = data.get('style')
        custom_lora_url = data.get('lora_url')
        
        # Validate required fields
        if not image_url:
            return jsonify({'success': False, 'error': 'image_url is required'}), 400
        
        if not style:
            return jsonify({'success': False, 'error': 'style is required'}), 400
        
        # Validate image URL
        if not image_url.startswith(('http://', 'https://')):
            return jsonify({'success': False, 'error': 'Invalid image URL'}), 400
        
        # Get style configuration
        style_config = get_style_config(style, custom_lora_url)
        
        # Prepare generation parameters
        generate_params = {
            'json': {
                'imageUrl': image_url,
                'prompt': style_config['prompt']
            }
        }
        
        if style_config['lora_url']:
            generate_params['json']['loraUrl'] = style_config['lora_url']
        
        # Make request to FAL API
        generate_url = f"https://fal-kontext-demo.vercel.app/api/trpc/generateImageStream?input={requests.utils.quote(json.dumps(generate_params))}"
        
        response = requests.get(generate_url, stream=True)
        if response.status_code != 200:
            return jsonify({'success': False, 'error': 'Failed to generate styled image'}), 500
        
        # Process streaming response
        uploaded_images = process_stream_response(response)
        
        return jsonify({
            'success': True,
            'originalImage': image_url,
            'style': style,
            'generatedImages': uploaded_images,
            'count': len(uploaded_images)
        })
        
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/')
def index():
    return jsonify({
        'message': 'Image Style Transfer API',
        'usage': 'POST /api/transform with JSON body: {"image_url": "...", "style": "...", "lora_url": "..."}',
        'styles': list(STYLES.keys())
    })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=7860, debug=True)